use super::{conv, Command as C}; use arrayvec::ArrayVec; use std::{mem, ops::Range}; #[derive(Clone, Copy, Debug, Default)] struct TextureSlotDesc { tex_target: super::BindTarget, sampler_index: Option, } pub(super) struct State { topology: u32, primitive: super::PrimitiveState, index_format: wgt::IndexFormat, index_offset: wgt::BufferAddress, vertex_buffers: [(super::VertexBufferDesc, Option); crate::MAX_VERTEX_BUFFERS], vertex_attributes: ArrayVec, color_targets: ArrayVec, stencil: super::StencilState, depth_bias: wgt::DepthBiasState, alpha_to_coverage_enabled: bool, samplers: [Option; super::MAX_SAMPLERS], texture_slots: [TextureSlotDesc; super::MAX_TEXTURE_SLOTS], render_size: wgt::Extent3d, resolve_attachments: ArrayVec<(u32, super::TextureView), { crate::MAX_COLOR_ATTACHMENTS }>, invalidate_attachments: ArrayVec, has_pass_label: bool, instance_vbuf_mask: usize, dirty_vbuf_mask: usize, active_first_instance: u32, first_instance_location: Option, push_constant_descs: ArrayVec, // The current state of the push constant data block. current_push_constant_data: [u32; super::MAX_PUSH_CONSTANTS], end_of_pass_timestamp: Option, } impl Default for State { fn default() -> Self { Self { topology: Default::default(), primitive: Default::default(), index_format: Default::default(), index_offset: Default::default(), vertex_buffers: Default::default(), vertex_attributes: Default::default(), color_targets: Default::default(), stencil: Default::default(), depth_bias: Default::default(), alpha_to_coverage_enabled: Default::default(), samplers: Default::default(), texture_slots: Default::default(), render_size: Default::default(), resolve_attachments: Default::default(), invalidate_attachments: Default::default(), has_pass_label: Default::default(), instance_vbuf_mask: Default::default(), dirty_vbuf_mask: Default::default(), active_first_instance: Default::default(), first_instance_location: Default::default(), push_constant_descs: Default::default(), current_push_constant_data: [0; super::MAX_PUSH_CONSTANTS], end_of_pass_timestamp: Default::default(), } } } impl super::CommandBuffer { fn clear(&mut self) { self.label = None; self.commands.clear(); self.data_bytes.clear(); self.queries.clear(); } fn add_marker(&mut self, marker: &str) -> Range { let start = self.data_bytes.len() as u32; self.data_bytes.extend(marker.as_bytes()); start..self.data_bytes.len() as u32 } fn add_push_constant_data(&mut self, data: &[u32]) -> Range { let data_raw = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const _, mem::size_of_val(data)) }; let start = self.data_bytes.len(); assert!(start < u32::MAX as usize); self.data_bytes.extend_from_slice(data_raw); let end = self.data_bytes.len(); assert!(end < u32::MAX as usize); (start as u32)..(end as u32) } } impl super::CommandEncoder { fn rebind_stencil_func(&mut self) { fn make(s: &super::StencilSide, face: u32) -> C { C::SetStencilFunc { face, function: s.function, reference: s.reference, read_mask: s.mask_read, } } let s = &self.state.stencil; if s.front.function == s.back.function && s.front.mask_read == s.back.mask_read && s.front.reference == s.back.reference { self.cmd_buffer .commands .push(make(&s.front, glow::FRONT_AND_BACK)); } else { self.cmd_buffer.commands.push(make(&s.front, glow::FRONT)); self.cmd_buffer.commands.push(make(&s.back, glow::BACK)); } } fn rebind_vertex_data(&mut self, first_instance: u32) { if self .private_caps .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT) { for (index, pair) in self.state.vertex_buffers.iter().enumerate() { if self.state.dirty_vbuf_mask & (1 << index) == 0 { continue; } let (buffer_desc, vb) = match *pair { // Not all dirty bindings are necessarily filled. Some may be unused. (_, None) => continue, (ref vb_desc, Some(ref vb)) => (vb_desc.clone(), vb), }; let instance_offset = match buffer_desc.step { wgt::VertexStepMode::Vertex => 0, wgt::VertexStepMode::Instance => first_instance * buffer_desc.stride, }; self.cmd_buffer.commands.push(C::SetVertexBuffer { index: index as u32, buffer: super::BufferBinding { raw: vb.raw, offset: vb.offset + instance_offset as wgt::BufferAddress, }, buffer_desc, }); self.state.dirty_vbuf_mask ^= 1 << index; } } else { let mut vbuf_mask = 0; for attribute in self.state.vertex_attributes.iter() { if self.state.dirty_vbuf_mask & (1 << attribute.buffer_index) == 0 { continue; } let (buffer_desc, vb) = match self.state.vertex_buffers[attribute.buffer_index as usize] { // Not all dirty bindings are necessarily filled. Some may be unused. (_, None) => continue, (ref vb_desc, Some(ref vb)) => (vb_desc.clone(), vb), }; let mut attribute_desc = attribute.clone(); attribute_desc.offset += vb.offset as u32; if buffer_desc.step == wgt::VertexStepMode::Instance { attribute_desc.offset += buffer_desc.stride * first_instance; } self.cmd_buffer.commands.push(C::SetVertexAttribute { buffer: Some(vb.raw), buffer_desc, attribute_desc, }); vbuf_mask |= 1 << attribute.buffer_index; } self.state.dirty_vbuf_mask ^= vbuf_mask; } } fn rebind_sampler_states(&mut self, dirty_textures: u32, dirty_samplers: u32) { for (texture_index, slot) in self.state.texture_slots.iter().enumerate() { if dirty_textures & (1 << texture_index) != 0 || slot .sampler_index .map_or(false, |si| dirty_samplers & (1 << si) != 0) { let sampler = slot .sampler_index .and_then(|si| self.state.samplers[si as usize]); self.cmd_buffer .commands .push(C::BindSampler(texture_index as u32, sampler)); } } } fn prepare_draw(&mut self, first_instance: u32) { // If we support fully featured instancing, we want to bind everything as normal // and let the draw call sort it out. let emulated_first_instance_value = if self .private_caps .contains(super::PrivateCapabilities::FULLY_FEATURED_INSTANCING) { 0 } else { first_instance }; if emulated_first_instance_value != self.state.active_first_instance { // rebind all per-instance buffers on first-instance change self.state.dirty_vbuf_mask |= self.state.instance_vbuf_mask; self.state.active_first_instance = emulated_first_instance_value; } if self.state.dirty_vbuf_mask != 0 { self.rebind_vertex_data(emulated_first_instance_value); } } #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation fn set_pipeline_inner(&mut self, inner: &super::PipelineInner) { self.cmd_buffer.commands.push(C::SetProgram(inner.program)); self.state.first_instance_location = inner.first_instance_location.clone(); self.state.push_constant_descs = inner.push_constant_descs.clone(); // rebind textures, if needed let mut dirty_textures = 0u32; for (texture_index, (slot, &sampler_index)) in self .state .texture_slots .iter_mut() .zip(inner.sampler_map.iter()) .enumerate() { if slot.sampler_index != sampler_index { slot.sampler_index = sampler_index; dirty_textures |= 1 << texture_index; } } if dirty_textures != 0 { self.rebind_sampler_states(dirty_textures, 0); } } } impl crate::CommandEncoder for super::CommandEncoder { unsafe fn begin_encoding(&mut self, label: crate::Label) -> Result<(), crate::DeviceError> { self.state = State::default(); self.cmd_buffer.label = label.map(str::to_string); Ok(()) } unsafe fn discard_encoding(&mut self) { self.cmd_buffer.clear(); } unsafe fn end_encoding(&mut self) -> Result { Ok(mem::take(&mut self.cmd_buffer)) } unsafe fn reset_all(&mut self, _command_buffers: I) { //TODO: could re-use the allocations in all these command buffers } unsafe fn transition_buffers<'a, T>(&mut self, barriers: T) where T: Iterator>, { if !self .private_caps .contains(super::PrivateCapabilities::MEMORY_BARRIERS) { return; } for bar in barriers { // GLES only synchronizes storage -> anything explicitly if !bar .usage .start .contains(crate::BufferUses::STORAGE_READ_WRITE) { continue; } self.cmd_buffer .commands .push(C::BufferBarrier(bar.buffer.raw.unwrap(), bar.usage.end)); } } unsafe fn transition_textures<'a, T>(&mut self, barriers: T) where T: Iterator>, { if !self .private_caps .contains(super::PrivateCapabilities::MEMORY_BARRIERS) { return; } let mut combined_usage = crate::TextureUses::empty(); for bar in barriers { // GLES only synchronizes storage -> anything explicitly if !bar .usage .start .contains(crate::TextureUses::STORAGE_READ_WRITE) { continue; } // unlike buffers, there is no need for a concrete texture // object to be bound anywhere for a barrier combined_usage |= bar.usage.end; } if !combined_usage.is_empty() { self.cmd_buffer .commands .push(C::TextureBarrier(combined_usage)); } } unsafe fn clear_buffer(&mut self, buffer: &super::Buffer, range: crate::MemoryRange) { self.cmd_buffer.commands.push(C::ClearBuffer { dst: buffer.clone(), dst_target: buffer.target, range, }); } unsafe fn copy_buffer_to_buffer( &mut self, src: &super::Buffer, dst: &super::Buffer, regions: T, ) where T: Iterator, { let (src_target, dst_target) = if src.target == dst.target { (glow::COPY_READ_BUFFER, glow::COPY_WRITE_BUFFER) } else { (src.target, dst.target) }; for copy in regions { self.cmd_buffer.commands.push(C::CopyBufferToBuffer { src: src.clone(), src_target, dst: dst.clone(), dst_target, copy, }) } } #[cfg(webgl)] unsafe fn copy_external_image_to_texture( &mut self, src: &wgt::ImageCopyExternalImage, dst: &super::Texture, dst_premultiplication: bool, regions: T, ) where T: Iterator, { let (dst_raw, dst_target) = dst.inner.as_native(); for copy in regions { self.cmd_buffer .commands .push(C::CopyExternalImageToTexture { src: src.clone(), dst: dst_raw, dst_target, dst_format: dst.format, dst_premultiplication, copy, }) } } unsafe fn copy_texture_to_texture( &mut self, src: &super::Texture, _src_usage: crate::TextureUses, dst: &super::Texture, regions: T, ) where T: Iterator, { let (src_raw, src_target) = src.inner.as_native(); let (dst_raw, dst_target) = dst.inner.as_native(); for mut copy in regions { copy.clamp_size_to_virtual(&src.copy_size, &dst.copy_size); self.cmd_buffer.commands.push(C::CopyTextureToTexture { src: src_raw, src_target, dst: dst_raw, dst_target, copy, }) } } unsafe fn copy_buffer_to_texture( &mut self, src: &super::Buffer, dst: &super::Texture, regions: T, ) where T: Iterator, { let (dst_raw, dst_target) = dst.inner.as_native(); for mut copy in regions { copy.clamp_size_to_virtual(&dst.copy_size); self.cmd_buffer.commands.push(C::CopyBufferToTexture { src: src.clone(), src_target: src.target, dst: dst_raw, dst_target, dst_format: dst.format, copy, }) } } unsafe fn copy_texture_to_buffer( &mut self, src: &super::Texture, _src_usage: crate::TextureUses, dst: &super::Buffer, regions: T, ) where T: Iterator, { let (src_raw, src_target) = src.inner.as_native(); for mut copy in regions { copy.clamp_size_to_virtual(&src.copy_size); self.cmd_buffer.commands.push(C::CopyTextureToBuffer { src: src_raw, src_target, src_format: src.format, dst: dst.clone(), dst_target: dst.target, copy, }) } } unsafe fn begin_query(&mut self, set: &super::QuerySet, index: u32) { let query = set.queries[index as usize]; self.cmd_buffer .commands .push(C::BeginQuery(query, set.target)); } unsafe fn end_query(&mut self, set: &super::QuerySet, _index: u32) { self.cmd_buffer.commands.push(C::EndQuery(set.target)); } unsafe fn write_timestamp(&mut self, set: &super::QuerySet, index: u32) { let query = set.queries[index as usize]; self.cmd_buffer.commands.push(C::TimestampQuery(query)); } unsafe fn reset_queries(&mut self, _set: &super::QuerySet, _range: Range) { //TODO: what do we do here? } unsafe fn copy_query_results( &mut self, set: &super::QuerySet, range: Range, buffer: &super::Buffer, offset: wgt::BufferAddress, _stride: wgt::BufferSize, ) { let start = self.cmd_buffer.queries.len(); self.cmd_buffer .queries .extend_from_slice(&set.queries[range.start as usize..range.end as usize]); let query_range = start as u32..self.cmd_buffer.queries.len() as u32; self.cmd_buffer.commands.push(C::CopyQueryResults { query_range, dst: buffer.clone(), dst_target: buffer.target, dst_offset: offset, }); } // render unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor) { debug_assert!(self.state.end_of_pass_timestamp.is_none()); if let Some(ref t) = desc.timestamp_writes { if let Some(index) = t.beginning_of_pass_write_index { unsafe { self.write_timestamp(t.query_set, index) } } self.state.end_of_pass_timestamp = t .end_of_pass_write_index .map(|index| t.query_set.queries[index as usize]); } self.state.render_size = desc.extent; self.state.resolve_attachments.clear(); self.state.invalidate_attachments.clear(); if let Some(label) = desc.label { let range = self.cmd_buffer.add_marker(label); self.cmd_buffer.commands.push(C::PushDebugGroup(range)); self.state.has_pass_label = true; } let rendering_to_external_framebuffer = desc .color_attachments .iter() .filter_map(|at| at.as_ref()) .any(|at| match at.target.view.inner { #[cfg(webgl)] super::TextureInner::ExternalFramebuffer { .. } => true, _ => false, }); if rendering_to_external_framebuffer && desc.color_attachments.len() != 1 { panic!("Multiple render attachments with external framebuffers are not supported."); } // `COLOR_ATTACHMENT0` to `COLOR_ATTACHMENT31` gives 32 possible color attachments. assert!(desc.color_attachments.len() <= 32); match desc .color_attachments .first() .filter(|at| at.is_some()) .and_then(|at| at.as_ref().map(|at| &at.target.view.inner)) { // default framebuffer (provided externally) Some(&super::TextureInner::DefaultRenderbuffer) => { self.cmd_buffer .commands .push(C::ResetFramebuffer { is_default: true }); } _ => { // set the framebuffer self.cmd_buffer .commands .push(C::ResetFramebuffer { is_default: false }); for (i, cat) in desc.color_attachments.iter().enumerate() { if let Some(cat) = cat.as_ref() { let attachment = glow::COLOR_ATTACHMENT0 + i as u32; self.cmd_buffer.commands.push(C::BindAttachment { attachment, view: cat.target.view.clone(), }); if let Some(ref rat) = cat.resolve_target { self.state .resolve_attachments .push((attachment, rat.view.clone())); } if !cat.ops.contains(crate::AttachmentOps::STORE) { self.state.invalidate_attachments.push(attachment); } } } if let Some(ref dsat) = desc.depth_stencil_attachment { let aspects = dsat.target.view.aspects; let attachment = match aspects { crate::FormatAspects::DEPTH => glow::DEPTH_ATTACHMENT, crate::FormatAspects::STENCIL => glow::STENCIL_ATTACHMENT, _ => glow::DEPTH_STENCIL_ATTACHMENT, }; self.cmd_buffer.commands.push(C::BindAttachment { attachment, view: dsat.target.view.clone(), }); if aspects.contains(crate::FormatAspects::DEPTH) && !dsat.depth_ops.contains(crate::AttachmentOps::STORE) { self.state .invalidate_attachments .push(glow::DEPTH_ATTACHMENT); } if aspects.contains(crate::FormatAspects::STENCIL) && !dsat.stencil_ops.contains(crate::AttachmentOps::STORE) { self.state .invalidate_attachments .push(glow::STENCIL_ATTACHMENT); } } } } let rect = crate::Rect { x: 0, y: 0, w: desc.extent.width as i32, h: desc.extent.height as i32, }; self.cmd_buffer.commands.push(C::SetScissor(rect.clone())); self.cmd_buffer.commands.push(C::SetViewport { rect, depth: 0.0..1.0, }); // issue the clears for (i, cat) in desc .color_attachments .iter() .filter_map(|at| at.as_ref()) .enumerate() { if !cat.ops.contains(crate::AttachmentOps::LOAD) { let c = &cat.clear_value; self.cmd_buffer.commands.push( match cat.target.view.format.sample_type(None, None).unwrap() { wgt::TextureSampleType::Float { .. } => C::ClearColorF { draw_buffer: i as u32, color: [c.r as f32, c.g as f32, c.b as f32, c.a as f32], is_srgb: cat.target.view.format.is_srgb(), }, wgt::TextureSampleType::Uint => C::ClearColorU( i as u32, [c.r as u32, c.g as u32, c.b as u32, c.a as u32], ), wgt::TextureSampleType::Sint => C::ClearColorI( i as u32, [c.r as i32, c.g as i32, c.b as i32, c.a as i32], ), wgt::TextureSampleType::Depth => unreachable!(), }, ); } } if !rendering_to_external_framebuffer { // set the draw buffers and states self.cmd_buffer .commands .push(C::SetDrawColorBuffers(desc.color_attachments.len() as u8)); } if let Some(ref dsat) = desc.depth_stencil_attachment { let clear_depth = !dsat.depth_ops.contains(crate::AttachmentOps::LOAD); let clear_stencil = !dsat.stencil_ops.contains(crate::AttachmentOps::LOAD); if clear_depth && clear_stencil { self.cmd_buffer.commands.push(C::ClearDepthAndStencil( dsat.clear_value.0, dsat.clear_value.1, )); } else if clear_depth { self.cmd_buffer .commands .push(C::ClearDepth(dsat.clear_value.0)); } else if clear_stencil { self.cmd_buffer .commands .push(C::ClearStencil(dsat.clear_value.1)); } } } unsafe fn end_render_pass(&mut self) { for (attachment, dst) in self.state.resolve_attachments.drain(..) { self.cmd_buffer.commands.push(C::ResolveAttachment { attachment, dst, size: self.state.render_size, }); } if !self.state.invalidate_attachments.is_empty() { self.cmd_buffer.commands.push(C::InvalidateAttachments( self.state.invalidate_attachments.clone(), )); self.state.invalidate_attachments.clear(); } if self.state.has_pass_label { self.cmd_buffer.commands.push(C::PopDebugGroup); self.state.has_pass_label = false; } self.state.instance_vbuf_mask = 0; self.state.dirty_vbuf_mask = 0; self.state.active_first_instance = 0; self.state.color_targets.clear(); for vat in &self.state.vertex_attributes { self.cmd_buffer .commands .push(C::UnsetVertexAttribute(vat.location)); } self.state.vertex_attributes.clear(); self.state.primitive = super::PrimitiveState::default(); if let Some(query) = self.state.end_of_pass_timestamp.take() { self.cmd_buffer.commands.push(C::TimestampQuery(query)); } } unsafe fn set_bind_group( &mut self, layout: &super::PipelineLayout, index: u32, group: &super::BindGroup, dynamic_offsets: &[wgt::DynamicOffset], ) { let mut do_index = 0; let mut dirty_textures = 0u32; let mut dirty_samplers = 0u32; let group_info = &layout.group_infos[index as usize]; for (binding_layout, raw_binding) in group_info.entries.iter().zip(group.contents.iter()) { let slot = group_info.binding_to_slot[binding_layout.binding as usize] as u32; match *raw_binding { super::RawBinding::Buffer { raw, offset: base_offset, size, } => { let mut offset = base_offset; let target = match binding_layout.ty { wgt::BindingType::Buffer { ty, has_dynamic_offset, min_binding_size: _, } => { if has_dynamic_offset { offset += dynamic_offsets[do_index] as i32; do_index += 1; } match ty { wgt::BufferBindingType::Uniform => glow::UNIFORM_BUFFER, wgt::BufferBindingType::Storage { .. } => { glow::SHADER_STORAGE_BUFFER } } } _ => unreachable!(), }; self.cmd_buffer.commands.push(C::BindBuffer { target, slot, buffer: raw, offset, size, }); } super::RawBinding::Sampler(sampler) => { dirty_samplers |= 1 << slot; self.state.samplers[slot as usize] = Some(sampler); } super::RawBinding::Texture { raw, target, aspects, ref mip_levels, } => { dirty_textures |= 1 << slot; self.state.texture_slots[slot as usize].tex_target = target; self.cmd_buffer.commands.push(C::BindTexture { slot, texture: raw, target, aspects, mip_levels: mip_levels.clone(), }); } super::RawBinding::Image(ref binding) => { self.cmd_buffer.commands.push(C::BindImage { slot, binding: binding.clone(), }); } } } self.rebind_sampler_states(dirty_textures, dirty_samplers); } unsafe fn set_push_constants( &mut self, _layout: &super::PipelineLayout, _stages: wgt::ShaderStages, offset_bytes: u32, data: &[u32], ) { // There is nothing preventing the user from trying to update a single value within // a vector or matrix in the set_push_constant call, as to the user, all of this is // just memory. However OpenGL does not allow partial uniform updates. // // As such, we locally keep a copy of the current state of the push constant memory // block. If the user tries to update a single value, we have the data to update the entirety // of the uniform. let start_words = offset_bytes / 4; let end_words = start_words + data.len() as u32; self.state.current_push_constant_data[start_words as usize..end_words as usize] .copy_from_slice(data); // We iterate over the uniform list as there may be multiple uniforms that need // updating from the same push constant memory (one for each shader stage). // // Additionally, any statically unused uniform descs will have been removed from this list // by OpenGL, so the uniform list is not contiguous. for uniform in self.state.push_constant_descs.iter().cloned() { let uniform_size_words = uniform.size_bytes / 4; let uniform_start_words = uniform.offset / 4; let uniform_end_words = uniform_start_words + uniform_size_words; // Is true if any word within the uniform binding was updated let needs_updating = start_words < uniform_end_words || uniform_start_words <= end_words; if needs_updating { let uniform_data = &self.state.current_push_constant_data [uniform_start_words as usize..uniform_end_words as usize]; let range = self.cmd_buffer.add_push_constant_data(uniform_data); self.cmd_buffer.commands.push(C::SetPushConstants { uniform, offset: range.start, }); } } } unsafe fn insert_debug_marker(&mut self, label: &str) { let range = self.cmd_buffer.add_marker(label); self.cmd_buffer.commands.push(C::InsertDebugMarker(range)); } unsafe fn begin_debug_marker(&mut self, group_label: &str) { let range = self.cmd_buffer.add_marker(group_label); self.cmd_buffer.commands.push(C::PushDebugGroup(range)); } unsafe fn end_debug_marker(&mut self) { self.cmd_buffer.commands.push(C::PopDebugGroup); } unsafe fn set_render_pipeline(&mut self, pipeline: &super::RenderPipeline) { self.state.topology = conv::map_primitive_topology(pipeline.primitive.topology); if self .private_caps .contains(super::PrivateCapabilities::VERTEX_BUFFER_LAYOUT) { for vat in pipeline.vertex_attributes.iter() { let vb = &pipeline.vertex_buffers[vat.buffer_index as usize]; // set the layout self.cmd_buffer.commands.push(C::SetVertexAttribute { buffer: None, buffer_desc: vb.clone(), attribute_desc: vat.clone(), }); } } else { for vat in &self.state.vertex_attributes { self.cmd_buffer .commands .push(C::UnsetVertexAttribute(vat.location)); } self.state.vertex_attributes.clear(); self.state.dirty_vbuf_mask = 0; // copy vertex attributes for vat in pipeline.vertex_attributes.iter() { //Note: we can invalidate more carefully here. self.state.dirty_vbuf_mask |= 1 << vat.buffer_index; self.state.vertex_attributes.push(vat.clone()); } } self.state.instance_vbuf_mask = 0; // copy vertex state for (index, (&mut (ref mut state_desc, _), pipe_desc)) in self .state .vertex_buffers .iter_mut() .zip(pipeline.vertex_buffers.iter()) .enumerate() { if pipe_desc.step == wgt::VertexStepMode::Instance { self.state.instance_vbuf_mask |= 1 << index; } if state_desc != pipe_desc { self.state.dirty_vbuf_mask |= 1 << index; *state_desc = pipe_desc.clone(); } } self.set_pipeline_inner(&pipeline.inner); // set primitive state let prim_state = conv::map_primitive_state(&pipeline.primitive); if prim_state != self.state.primitive { self.cmd_buffer .commands .push(C::SetPrimitive(prim_state.clone())); self.state.primitive = prim_state; } // set depth/stencil states let mut aspects = crate::FormatAspects::empty(); if pipeline.depth_bias != self.state.depth_bias { self.state.depth_bias = pipeline.depth_bias; self.cmd_buffer .commands .push(C::SetDepthBias(pipeline.depth_bias)); } if let Some(ref depth) = pipeline.depth { aspects |= crate::FormatAspects::DEPTH; self.cmd_buffer.commands.push(C::SetDepth(depth.clone())); } if let Some(ref stencil) = pipeline.stencil { aspects |= crate::FormatAspects::STENCIL; self.state.stencil = stencil.clone(); self.rebind_stencil_func(); if stencil.front.ops == stencil.back.ops && stencil.front.mask_write == stencil.back.mask_write { self.cmd_buffer.commands.push(C::SetStencilOps { face: glow::FRONT_AND_BACK, write_mask: stencil.front.mask_write, ops: stencil.front.ops.clone(), }); } else { self.cmd_buffer.commands.push(C::SetStencilOps { face: glow::FRONT, write_mask: stencil.front.mask_write, ops: stencil.front.ops.clone(), }); self.cmd_buffer.commands.push(C::SetStencilOps { face: glow::BACK, write_mask: stencil.back.mask_write, ops: stencil.back.ops.clone(), }); } } self.cmd_buffer .commands .push(C::ConfigureDepthStencil(aspects)); // set multisampling state if pipeline.alpha_to_coverage_enabled != self.state.alpha_to_coverage_enabled { self.state.alpha_to_coverage_enabled = pipeline.alpha_to_coverage_enabled; self.cmd_buffer .commands .push(C::SetAlphaToCoverage(pipeline.alpha_to_coverage_enabled)); } // set blend states if self.state.color_targets[..] != pipeline.color_targets[..] { if pipeline .color_targets .iter() .skip(1) .any(|ct| *ct != pipeline.color_targets[0]) { for (index, ct) in pipeline.color_targets.iter().enumerate() { self.cmd_buffer.commands.push(C::SetColorTarget { draw_buffer_index: Some(index as u32), desc: ct.clone(), }); } } else { self.cmd_buffer.commands.push(C::SetColorTarget { draw_buffer_index: None, desc: pipeline.color_targets.first().cloned().unwrap_or_default(), }); } } self.state.color_targets.clear(); for ct in pipeline.color_targets.iter() { self.state.color_targets.push(ct.clone()); } } unsafe fn set_index_buffer<'a>( &mut self, binding: crate::BufferBinding<'a, super::Api>, format: wgt::IndexFormat, ) { self.state.index_offset = binding.offset; self.state.index_format = format; self.cmd_buffer .commands .push(C::SetIndexBuffer(binding.buffer.raw.unwrap())); } unsafe fn set_vertex_buffer<'a>( &mut self, index: u32, binding: crate::BufferBinding<'a, super::Api>, ) { self.state.dirty_vbuf_mask |= 1 << index; let (_, ref mut vb) = self.state.vertex_buffers[index as usize]; *vb = Some(super::BufferBinding { raw: binding.buffer.raw.unwrap(), offset: binding.offset, }); } unsafe fn set_viewport(&mut self, rect: &crate::Rect, depth: Range) { self.cmd_buffer.commands.push(C::SetViewport { rect: crate::Rect { x: rect.x as i32, y: rect.y as i32, w: rect.w as i32, h: rect.h as i32, }, depth, }); } unsafe fn set_scissor_rect(&mut self, rect: &crate::Rect) { self.cmd_buffer.commands.push(C::SetScissor(crate::Rect { x: rect.x as i32, y: rect.y as i32, w: rect.w as i32, h: rect.h as i32, })); } unsafe fn set_stencil_reference(&mut self, value: u32) { self.state.stencil.front.reference = value; self.state.stencil.back.reference = value; self.rebind_stencil_func(); } unsafe fn set_blend_constants(&mut self, color: &[f32; 4]) { self.cmd_buffer.commands.push(C::SetBlendConstant(*color)); } unsafe fn draw( &mut self, first_vertex: u32, vertex_count: u32, first_instance: u32, instance_count: u32, ) { self.prepare_draw(first_instance); #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::Draw { topology: self.state.topology, first_vertex, vertex_count, first_instance, instance_count, first_instance_location: self.state.first_instance_location.clone(), }); } unsafe fn draw_indexed( &mut self, first_index: u32, index_count: u32, base_vertex: i32, first_instance: u32, instance_count: u32, ) { self.prepare_draw(first_instance); let (index_size, index_type) = match self.state.index_format { wgt::IndexFormat::Uint16 => (2, glow::UNSIGNED_SHORT), wgt::IndexFormat::Uint32 => (4, glow::UNSIGNED_INT), }; let index_offset = self.state.index_offset + index_size * first_index as wgt::BufferAddress; #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::DrawIndexed { topology: self.state.topology, index_type, index_offset, index_count, base_vertex, first_instance, instance_count, first_instance_location: self.state.first_instance_location.clone(), }); } unsafe fn draw_indirect( &mut self, buffer: &super::Buffer, offset: wgt::BufferAddress, draw_count: u32, ) { self.prepare_draw(0); for draw in 0..draw_count as wgt::BufferAddress { let indirect_offset = offset + draw * mem::size_of::() as wgt::BufferAddress; #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::DrawIndirect { topology: self.state.topology, indirect_buf: buffer.raw.unwrap(), indirect_offset, first_instance_location: self.state.first_instance_location.clone(), }); } } unsafe fn draw_indexed_indirect( &mut self, buffer: &super::Buffer, offset: wgt::BufferAddress, draw_count: u32, ) { self.prepare_draw(0); let index_type = match self.state.index_format { wgt::IndexFormat::Uint16 => glow::UNSIGNED_SHORT, wgt::IndexFormat::Uint32 => glow::UNSIGNED_INT, }; for draw in 0..draw_count as wgt::BufferAddress { let indirect_offset = offset + draw * mem::size_of::() as wgt::BufferAddress; #[allow(clippy::clone_on_copy)] // False positive when cloning glow::UniformLocation self.cmd_buffer.commands.push(C::DrawIndexedIndirect { topology: self.state.topology, index_type, indirect_buf: buffer.raw.unwrap(), indirect_offset, first_instance_location: self.state.first_instance_location.clone(), }); } } unsafe fn draw_indirect_count( &mut self, _buffer: &super::Buffer, _offset: wgt::BufferAddress, _count_buffer: &super::Buffer, _count_offset: wgt::BufferAddress, _max_count: u32, ) { unreachable!() } unsafe fn draw_indexed_indirect_count( &mut self, _buffer: &super::Buffer, _offset: wgt::BufferAddress, _count_buffer: &super::Buffer, _count_offset: wgt::BufferAddress, _max_count: u32, ) { unreachable!() } // compute unsafe fn begin_compute_pass(&mut self, desc: &crate::ComputePassDescriptor) { debug_assert!(self.state.end_of_pass_timestamp.is_none()); if let Some(ref t) = desc.timestamp_writes { if let Some(index) = t.beginning_of_pass_write_index { unsafe { self.write_timestamp(t.query_set, index) } } self.state.end_of_pass_timestamp = t .end_of_pass_write_index .map(|index| t.query_set.queries[index as usize]); } if let Some(label) = desc.label { let range = self.cmd_buffer.add_marker(label); self.cmd_buffer.commands.push(C::PushDebugGroup(range)); self.state.has_pass_label = true; } } unsafe fn end_compute_pass(&mut self) { if self.state.has_pass_label { self.cmd_buffer.commands.push(C::PopDebugGroup); self.state.has_pass_label = false; } if let Some(query) = self.state.end_of_pass_timestamp.take() { self.cmd_buffer.commands.push(C::TimestampQuery(query)); } } unsafe fn set_compute_pipeline(&mut self, pipeline: &super::ComputePipeline) { self.set_pipeline_inner(&pipeline.inner); } unsafe fn dispatch(&mut self, count: [u32; 3]) { self.cmd_buffer.commands.push(C::Dispatch(count)); } unsafe fn dispatch_indirect(&mut self, buffer: &super::Buffer, offset: wgt::BufferAddress) { self.cmd_buffer.commands.push(C::DispatchIndirect { indirect_buf: buffer.raw.unwrap(), indirect_offset: offset, }); } unsafe fn build_acceleration_structures<'a, T>( &mut self, _descriptor_count: u32, _descriptors: T, ) where super::Api: 'a, T: IntoIterator>, { unimplemented!() } unsafe fn place_acceleration_structure_barrier( &mut self, _barriers: crate::AccelerationStructureBarrier, ) { unimplemented!() } }