diff --git a/Cargo.lock b/Cargo.lock index aededcec..b775e9cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -868,7 +868,7 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sdl3" -version = "0.14.27" +version = "0.14.26" dependencies = [ "bitflags 2.9.0", "c_vec", @@ -882,6 +882,7 @@ dependencies = [ "sdl3-image-sys", "sdl3-sys", "sdl3-ttf-sys", + "static_assertions", "wgpu", ] diff --git a/Cargo.toml b/Cargo.toml index f87ab4d9..f77ce670 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ rand = "0.8.5" wgpu = { version = "24.0.0", features = ["spirv"] } pollster = "0.4.0" env_logger = "0.11.6" +static_assertions = "1.1.0" [features] diff --git a/examples/gpu-clear.rs b/examples/gpu-clear.rs index 6731fdc4..b5a7fadb 100644 --- a/examples/gpu-clear.rs +++ b/examples/gpu-clear.rs @@ -17,7 +17,8 @@ pub fn main() -> Result<(), Box> { // by default, and we specify that our shaders will be SPIR-V ones (even through we // aren't using any shaders) // We'll also turn on debug mode to true, so we get debug stuff - let gpu = sdl3::gpu::Device::new(sdl3::gpu::ShaderFormat::SpirV, true)?.with_window(&window)?; + let gpu = sdl3::gpu::OwnedDevice::new(sdl3::gpu::ShaderFormat::SPIRV, true)?; + gpu.claim_window(&window)?; let mut event_pump = sdl_context.event_pump()?; println!( @@ -45,15 +46,15 @@ pub fn main() -> Result<(), Box> { let color_targets = [ sdl3::gpu::ColorTargetInfo::default() .with_texture(&swapchain) // Use swapchain texture - .with_load_op(sdl3::gpu::LoadOp::Clear) // Clear when load - .with_store_op(sdl3::gpu::StoreOp::Store) // Store back + .with_load_op(sdl3::gpu::LoadOp::CLEAR) // Clear when load + .with_store_op(sdl3::gpu::StoreOp::STORE) // Store back .with_clear_color(sdl3::pixels::Color::RGB(5, 3, 255)), //blue with small RG bias ]; // Here we do all (none) of our drawing (clearing the screen) - let render_pass = gpu.begin_render_pass(&command_buffer, &color_targets, None)?; - // Do absolutely nothing -- this clears the screen because of the defined operations above - // which are ALWAYS done even through we just created and ended a render pass - gpu.end_render_pass(render_pass); + command_buffer.render_pass(&color_targets, None, |_cmd, _pass| { + // Do absolutely nothing -- this clears the screen because of the defined operations above + // which are ALWAYS done even through we just created and ended a render pass + })?; command_buffer.submit()?; } else { // Swapchain unavailable, cancel work diff --git a/examples/gpu-cube.rs b/examples/gpu-cube.rs index dd375125..e77e19dc 100644 --- a/examples/gpu-cube.rs +++ b/examples/gpu-cube.rs @@ -3,11 +3,11 @@ use sdl3::{ gpu::{ Buffer, BufferBinding, BufferRegion, BufferUsageFlags, ColorTargetDescription, ColorTargetInfo, CompareOp, CopyPass, CullMode, DepthStencilState, DepthStencilTargetInfo, - Device, FillMode, GraphicsPipelineTargetInfo, IndexElementSize, LoadOp, PrimitiveType, - RasterizerState, SampleCount, ShaderFormat, ShaderStage, StoreOp, TextureCreateInfo, - TextureFormat, TextureType, TextureUsage, TransferBuffer, TransferBufferLocation, - TransferBufferUsage, VertexAttribute, VertexBufferDescription, VertexElementFormat, - VertexInputRate, VertexInputState, + FillMode, GraphicsPipelineTargetInfo, IndexElementSize, LoadOp, Owned, OwnedDevice, + PrimitiveType, RasterizerState, SampleCount, ShaderFormat, ShaderStage, StoreOp, + TextureCreateInfo, TextureFormat, TextureType, TextureUsage, TransferBuffer, + TransferBufferLocation, TransferBufferUsage, VertexAttribute, VertexBufferDescription, + VertexElementFormat, VertexInputRate, VertexInputState, }, keyboard::Keycode, pixels::Color, @@ -88,17 +88,17 @@ pub fn main() -> Result<(), Box> { .build() .map_err(|e| e.to_string())?; - let gpu = Device::new( - ShaderFormat::SpirV | ShaderFormat::Dxil | ShaderFormat::Dxbc | ShaderFormat::MetalLib, + let gpu = OwnedDevice::new( + ShaderFormat::SPIRV | ShaderFormat::DXIL | ShaderFormat::DXBC | ShaderFormat::METALLIB, true, - )? - .with_window(&window)?; + )?; + gpu.claim_window(&window)?; // Our shaders, require to be precompiled by a SPIR-V compiler beforehand let vert_shader = gpu .create_shader() .with_code( - ShaderFormat::SpirV, + ShaderFormat::SPIRV, include_bytes!("shaders/cube.vert.spv"), ShaderStage::Vertex, ) @@ -108,7 +108,7 @@ pub fn main() -> Result<(), Box> { let frag_shader = gpu .create_shader() .with_code( - ShaderFormat::SpirV, + ShaderFormat::SPIRV, include_bytes!("shaders/cube.frag.spv"), ShaderStage::Fragment, ) @@ -156,7 +156,7 @@ pub fn main() -> Result<(), Box> { ColorTargetDescription::new().with_format(swapchain_format) ]) .with_has_depth_stencil_target(true) - .with_depth_stencil_format(TextureFormat::D16Unorm), + .with_depth_stencil_format(TextureFormat::D16_UNORM), ) .build()?; @@ -168,50 +168,52 @@ pub fn main() -> Result<(), Box> { // our vertices or indices since we will be transferring both with it. let vertices_len_bytes = CUBE_VERTICES.len() * size_of::(); let indices_len_bytes = CUBE_INDICES.len() * size_of::(); - let transfer_buffer = gpu + let mut transfer_buffer = gpu .create_transfer_buffer() .with_size(vertices_len_bytes.max(indices_len_bytes) as u32) - .with_usage(TransferBufferUsage::Upload) + .with_usage(TransferBufferUsage::UPLOAD) .build()?; // We need to start a copy pass in order to transfer data to the GPU - let copy_commands = gpu.acquire_command_buffer()?; - let copy_pass = gpu.begin_copy_pass(©_commands)?; - - // Create GPU buffers to hold our vertices and indices and transfer data to them - let vertex_buffer = create_buffer_with_data( - &gpu, - &transfer_buffer, - ©_pass, - BufferUsageFlags::Vertex, - &CUBE_VERTICES, - )?; - let index_buffer = create_buffer_with_data( - &gpu, - &transfer_buffer, - ©_pass, - BufferUsageFlags::Index, - &CUBE_INDICES, - )?; + let mut copy_commands = gpu.acquire_command_buffer()?; + let (vertex_buffer, index_buffer) = copy_commands.copy_pass(|_cmd, copy_pass| { + // Create GPU buffers to hold our vertices and indices and transfer data to them + let vertex_buffer = create_buffer_with_data( + &gpu, + &mut transfer_buffer, + ©_pass, + BufferUsageFlags::VERTEX, + &CUBE_VERTICES, + ) + .unwrap(); + let index_buffer = create_buffer_with_data( + &gpu, + &mut transfer_buffer, + ©_pass, + BufferUsageFlags::INDEX, + &CUBE_INDICES, + ) + .unwrap(); + // We're done with the transfer buffer now, so release it. + drop(transfer_buffer); - // We're done with the transfer buffer now, so release it. - drop(transfer_buffer); + (vertex_buffer, index_buffer) + })?; // Now complete and submit the copy pass commands to actually do the transfer work - gpu.end_copy_pass(copy_pass); copy_commands.submit()?; // We'll need to allocate a texture buffer for our depth buffer for depth testing to work let mut depth_texture = gpu.create_texture( - TextureCreateInfo::new() + &TextureCreateInfo::new() .with_type(TextureType::_2D) .with_width(WINDOW_SIZE) .with_height(WINDOW_SIZE) .with_layer_count_or_depth(1) .with_num_levels(1) .with_sample_count(SampleCount::NoMultiSampling) - .with_format(TextureFormat::D16Unorm) - .with_usage(TextureUsage::Sampler | TextureUsage::DepthStencilTarget), + .with_format(TextureFormat::D16_UNORM) + .with_usage(TextureUsage::SAMPLER | TextureUsage::DEPTH_STENCIL_TARGET), )?; let mut rotation = 45.0f32; @@ -238,8 +240,8 @@ pub fn main() -> Result<(), Box> { // Again, like in gpu-clear.rs, we'd want to define basic operations for our cube let color_targets = [ColorTargetInfo::default() .with_texture(&swapchain) - .with_load_op(LoadOp::Clear) - .with_store_op(StoreOp::Store) + .with_load_op(LoadOp::CLEAR) + .with_store_op(StoreOp::STORE) .with_clear_color(Color::RGB(128, 128, 128))]; // This time, however, we want depth testing, so we need to also target a depth texture buffer let depth_target = DepthStencilTargetInfo::new() @@ -247,38 +249,35 @@ pub fn main() -> Result<(), Box> { .with_cycle(true) .with_clear_depth(1.0) .with_clear_stencil(0) - .with_load_op(LoadOp::Clear) - .with_store_op(StoreOp::Store) - .with_stencil_load_op(LoadOp::Clear) - .with_stencil_store_op(StoreOp::Store); - let render_pass = - gpu.begin_render_pass(&command_buffer, &color_targets, Some(&depth_target))?; - - // Screen is cleared below due to the color target info - render_pass.bind_graphics_pipeline(&pipeline); - - // Now we'll bind our buffers and draw the cube - render_pass.bind_vertex_buffers( - 0, - &[BufferBinding::new() - .with_buffer(&vertex_buffer) - .with_offset(0)], - ); - render_pass.bind_index_buffer( - &BufferBinding::new() - .with_buffer(&index_buffer) - .with_offset(0), - IndexElementSize::_16Bit, - ); + .with_load_op(LoadOp::CLEAR) + .with_store_op(StoreOp::STORE) + .with_stencil_load_op(LoadOp::CLEAR) + .with_stencil_store_op(StoreOp::STORE); + command_buffer.render_pass( + &color_targets, + Some(&depth_target), + |command_buffer, render_pass| { + // Screen is cleared below due to the color target info + render_pass.bind_graphics_pipeline(&pipeline); - // Set the rotation uniform for our cube vert shader - command_buffer.push_vertex_uniform_data(0, &rotation); - rotation += 0.1f32; + // Now we'll bind our buffers and draw the cube + render_pass.bind_vertex_buffers( + 0, + &[BufferBinding::new().with_buffer(&vertex_buffer)], + ); + render_pass.bind_index_buffer( + &BufferBinding::new().with_buffer(&index_buffer), + IndexElementSize::_16BIT, + ); - // Finally, draw the cube - render_pass.draw_indexed_primitives(CUBE_INDICES.len() as u32, 1, 0, 0, 0); + // Set the rotation uniform for our cube vert shader + command_buffer.push_vertex_uniform_data(0, &rotation); + rotation += 0.1f32; - gpu.end_render_pass(render_pass); + // Finally, draw the cube + render_pass.draw_indexed_primitives(CUBE_INDICES.len() as u32, 1, 0, 0, 0); + }, + )?; command_buffer.submit()?; } else { // Swapchain unavailable, cancel work @@ -290,13 +289,13 @@ pub fn main() -> Result<(), Box> { } /// Creates a GPU buffer and uploads data to it using the given `copy_pass` and `transfer_buffer`. -fn create_buffer_with_data( - gpu: &Device, - transfer_buffer: &TransferBuffer, +fn create_buffer_with_data<'gpu, T: Copy>( + gpu: &'gpu OwnedDevice, + transfer_buffer: &mut Owned<'_, TransferBuffer>, copy_pass: &CopyPass, usage: BufferUsageFlags, data: &[T], -) -> Result { +) -> Result, Error> { // Figure out the length of the data in bytes let len_bytes = data.len() * std::mem::size_of::(); @@ -312,26 +311,20 @@ fn create_buffer_with_data( // Note: We set `cycle` to true since we're reusing the same transfer buffer to // initialize both the vertex and index buffer. This makes SDL synchronize the transfers // so that one doesn't interfere with the other. - let mut map = transfer_buffer.map::(gpu, true); - let mem = map.mem_mut(); - for (index, &value) in data.iter().enumerate() { - mem[index] = value; - } - - // Now unmap the memory since we're done copying - map.unmap(); + transfer_buffer.mapped_mut(true, |bytes| unsafe { + std::ptr::copy_nonoverlapping::( + data.as_ptr() as *const u8, + bytes.as_mut_ptr(), + len_bytes, + ); + })?; // Finally, add a command to the copy pass to upload this data to the GPU // // Note: We also set `cycle` to true here for the same reason. copy_pass.upload_to_gpu_buffer( - TransferBufferLocation::new() - .with_offset(0) - .with_transfer_buffer(transfer_buffer), - BufferRegion::new() - .with_offset(0) - .with_size(len_bytes as u32) - .with_buffer(&buffer), + transfer_buffer.get(0..), + buffer.get(0..len_bytes as u32), true, ); diff --git a/examples/gpu-texture.rs b/examples/gpu-texture.rs index 710d2659..fe1082f6 100644 --- a/examples/gpu-texture.rs +++ b/examples/gpu-texture.rs @@ -1,13 +1,13 @@ use sdl3::{ event::Event, gpu::{ - Buffer, BufferBinding, BufferRegion, BufferUsageFlags, ColorTargetDescription, - ColorTargetInfo, CompareOp, CopyPass, CullMode, DepthStencilState, DepthStencilTargetInfo, - Device, FillMode, Filter, GraphicsPipelineTargetInfo, IndexElementSize, LoadOp, - PrimitiveType, RasterizerState, SampleCount, SamplerAddressMode, SamplerCreateInfo, - SamplerMipmapMode, ShaderFormat, ShaderStage, StoreOp, Texture, TextureCreateInfo, - TextureFormat, TextureRegion, TextureSamplerBinding, TextureTransferInfo, TextureType, - TextureUsage, TransferBuffer, TransferBufferLocation, TransferBufferUsage, VertexAttribute, + Buffer, BufferBinding, BufferUsageFlags, ColorTargetDescription, ColorTargetInfo, + CompareOp, CopyPass, CullMode, DepthStencilState, DepthStencilTargetInfo, FillMode, Filter, + GraphicsPipelineTargetInfo, IndexElementSize, LoadOp, Owned, OwnedDevice, PrimitiveType, + RasterizerState, SampleCount, SamplerAddressMode, SamplerCreateInfo, SamplerMipmapMode, + ShaderFormat, ShaderStage, StoreOp, Texture, TextureCreateInfo, TextureFormat, + TextureRegion, TextureSamplerBinding, TextureTransferInfo, TextureType, TextureUsage, + TransferBuffer, TransferBufferLocation, TransferBufferUsage, VertexAttribute, VertexBufferDescription, VertexElementFormat, VertexInputRate, VertexInputState, }, keyboard::Keycode, @@ -193,17 +193,17 @@ pub fn main() -> Result<(), Box> { .build() .map_err(|e| e.to_string())?; - let gpu = sdl3::gpu::Device::new( - ShaderFormat::SpirV | ShaderFormat::Dxil | ShaderFormat::Dxbc | ShaderFormat::MetalLib, + let gpu = sdl3::gpu::OwnedDevice::new( + ShaderFormat::SPIRV | ShaderFormat::DXIL | ShaderFormat::DXBC | ShaderFormat::METALLIB, true, - )? - .with_window(&window)?; + )?; + gpu.claim_window(&window)?; // Our shaders, require to be precompiled by a SPIR-V compiler beforehand let vert_shader = gpu .create_shader() .with_code( - ShaderFormat::SpirV, + ShaderFormat::SPIRV, include_bytes!("shaders/cube-texture.vert.spv"), ShaderStage::Vertex, ) @@ -213,7 +213,7 @@ pub fn main() -> Result<(), Box> { let frag_shader = gpu .create_shader() .with_code( - ShaderFormat::SpirV, + ShaderFormat::SPIRV, include_bytes!("shaders/cube-texture.frag.spv"), ShaderStage::Fragment, ) @@ -269,7 +269,7 @@ pub fn main() -> Result<(), Box> { ColorTargetDescription::new().with_format(swapchain_format) ]) .with_has_depth_stencil_target(true) - .with_depth_stencil_format(TextureFormat::D16Unorm), + .with_depth_stencil_format(TextureFormat::D16_UNORM), ) .build()?; @@ -281,64 +281,76 @@ pub fn main() -> Result<(), Box> { // our vertices or indices since we will be transferring both with it. let vertices_len_bytes = CUBE_VERTICES.len() * size_of::(); let indices_len_bytes = CUBE_INDICES.len() * size_of::(); - let transfer_buffer = gpu + let mut transfer_buffer = gpu .create_transfer_buffer() .with_size(vertices_len_bytes.max(indices_len_bytes) as u32) - .with_usage(TransferBufferUsage::Upload) + .with_usage(TransferBufferUsage::UPLOAD) .build()?; // We need to start a copy pass in order to transfer data to the GPU - let copy_commands = gpu.acquire_command_buffer()?; - let copy_pass = gpu.begin_copy_pass(©_commands)?; - - // Create GPU buffers to hold our vertices and indices and transfer data to them - let vertex_buffer = create_buffer_with_data( - &gpu, - &transfer_buffer, - ©_pass, - BufferUsageFlags::Vertex, - &CUBE_VERTICES, - )?; - let index_buffer = create_buffer_with_data( - &gpu, - &transfer_buffer, - ©_pass, - BufferUsageFlags::Index, - &CUBE_INDICES, - )?; - - // We're done with the transfer buffer now, so release it. - drop(transfer_buffer); - - // Load up a texture to put on the cube - let cube_texture = create_texture_from_image(&gpu, "./assets/texture.bmp", ©_pass)?; - - // And configure a sampler for pulling pixels from that texture in the frag shader - let cube_texture_sampler = gpu.create_sampler( - SamplerCreateInfo::new() - .with_min_filter(Filter::Nearest) - .with_mag_filter(Filter::Nearest) - .with_mipmap_mode(SamplerMipmapMode::Nearest) - .with_address_mode_u(SamplerAddressMode::Repeat) - .with_address_mode_v(SamplerAddressMode::Repeat) - .with_address_mode_w(SamplerAddressMode::Repeat), - )?; + let mut copy_commands = gpu.acquire_command_buffer()?; + let (vertex_buffer, index_buffer, cube_texture, cube_texture_sampler) = copy_commands + .copy_pass(|_cmd, copy_pass| { + // Create GPU buffers to hold our vertices and indices and transfer data to them + let vertex_buffer = create_buffer_with_data( + &gpu, + &mut transfer_buffer, + ©_pass, + BufferUsageFlags::VERTEX, + &CUBE_VERTICES, + ) + .unwrap(); + let index_buffer = create_buffer_with_data( + &gpu, + &mut transfer_buffer, + ©_pass, + BufferUsageFlags::INDEX, + &CUBE_INDICES, + ) + .unwrap(); + + // We're done with the transfer buffer now, so release it. + drop(transfer_buffer); + + // Load up a texture to put on the cube + let cube_texture = + create_texture_from_image(&gpu, "./assets/texture.bmp", ©_pass).unwrap(); + + // And configure a sampler for pulling pixels from that texture in the frag shader + let cube_texture_sampler = gpu + .create_sampler( + SamplerCreateInfo::new() + .with_min_filter(Filter::Nearest) + .with_mag_filter(Filter::Nearest) + .with_mipmap_mode(SamplerMipmapMode::Nearest) + .with_address_mode_u(SamplerAddressMode::Repeat) + .with_address_mode_v(SamplerAddressMode::Repeat) + .with_address_mode_w(SamplerAddressMode::Repeat), + ) + .unwrap(); + + ( + vertex_buffer, + index_buffer, + cube_texture, + cube_texture_sampler, + ) + })?; // Now complete and submit the copy pass commands to actually do the transfer work - gpu.end_copy_pass(copy_pass); copy_commands.submit()?; // We'll need to allocate a texture buffer for our depth buffer for depth testing to work let mut depth_texture = gpu.create_texture( - TextureCreateInfo::new() + &TextureCreateInfo::new() .with_type(TextureType::_2D) .with_width(WINDOW_SIZE) .with_height(WINDOW_SIZE) .with_layer_count_or_depth(1) .with_num_levels(1) .with_sample_count(SampleCount::NoMultiSampling) - .with_format(TextureFormat::D16Unorm) - .with_usage(TextureUsage::Sampler | TextureUsage::DepthStencilTarget), + .with_format(TextureFormat::D16_UNORM) + .with_usage(TextureUsage::SAMPLER | TextureUsage::DEPTH_STENCIL_TARGET), )?; let mut rotation = 45.0f32; @@ -365,8 +377,8 @@ pub fn main() -> Result<(), Box> { // Again, like in gpu-clear.rs, we'd want to define basic operations for our cube let color_targets = [ColorTargetInfo::default() .with_texture(&swapchain) - .with_load_op(LoadOp::Clear) - .with_store_op(StoreOp::Store) + .with_load_op(LoadOp::CLEAR) + .with_store_op(StoreOp::STORE) .with_clear_color(Color::RGB(128, 128, 128))]; // This time, however, we want depth testing, so we need to also target a depth texture buffer let depth_target = DepthStencilTargetInfo::new() @@ -374,44 +386,45 @@ pub fn main() -> Result<(), Box> { .with_cycle(true) .with_clear_depth(1.0) .with_clear_stencil(0) - .with_load_op(LoadOp::Clear) - .with_store_op(StoreOp::Store) - .with_stencil_load_op(LoadOp::Clear) - .with_stencil_store_op(StoreOp::Store); - let render_pass = - gpu.begin_render_pass(&command_buffer, &color_targets, Some(&depth_target))?; - - // Screen is cleared below due to the color target info - render_pass.bind_graphics_pipeline(&pipeline); - - // Now we'll bind our buffers/sampler and draw the cube - render_pass.bind_vertex_buffers( - 0, - &[BufferBinding::new() - .with_buffer(&vertex_buffer) - .with_offset(0)], - ); - render_pass.bind_index_buffer( - &BufferBinding::new() - .with_buffer(&index_buffer) - .with_offset(0), - IndexElementSize::_16Bit, - ); - render_pass.bind_fragment_samplers( - 0, - &[TextureSamplerBinding::new() - .with_texture(&cube_texture) - .with_sampler(&cube_texture_sampler)], - ); - - // Set the rotation uniform for our cube vert shader - command_buffer.push_vertex_uniform_data(0, &rotation); - rotation += 0.1f32; - - // Finally, draw the cube - render_pass.draw_indexed_primitives(CUBE_INDICES.len() as u32, 1, 0, 0, 0); + .with_load_op(LoadOp::CLEAR) + .with_store_op(StoreOp::STORE) + .with_stencil_load_op(LoadOp::CLEAR) + .with_stencil_store_op(StoreOp::STORE); + + command_buffer.render_pass( + &color_targets, + Some(&depth_target), + |command_buffer, render_pass| { + // Screen is cleared below due to the color target info + render_pass.bind_graphics_pipeline(&pipeline); + + // Now we'll bind our buffers/sampler and draw the cube + render_pass.bind_vertex_buffers( + 0, + &[BufferBinding::new() + .with_buffer(&vertex_buffer) + .with_offset(0)], + ); + render_pass.bind_index_buffer( + &BufferBinding::new() + .with_buffer(&index_buffer) + .with_offset(0), + IndexElementSize::_16BIT, + ); + render_pass.bind_fragment_samplers( + 0, + &[cube_texture.with_sampler(&cube_texture_sampler)], + ); + + // Set the rotation uniform for our cube vert shader + command_buffer.push_vertex_uniform_data(0, &rotation); + rotation += 0.1f32; + + // Finally, draw the cube + render_pass.draw_indexed_primitives(CUBE_INDICES.len() as u32, 1, 0, 0, 0); + }, + )?; - gpu.end_render_pass(render_pass); command_buffer.submit()?; } else { // Swapchain unavailable, cancel work @@ -422,38 +435,42 @@ pub fn main() -> Result<(), Box> { Ok(()) } -fn create_texture_from_image( - gpu: &Device, +fn create_texture_from_image<'gpu>( + gpu: &'gpu OwnedDevice, image_path: impl AsRef, copy_pass: &CopyPass, -) -> Result, Error> { +) -> Result, Error> { let image = Surface::load_bmp(image_path.as_ref())?; let image_size = image.size(); let size_bytes = image.pixel_format().byte_size_per_pixel() as u32 * image_size.0 * image_size.1; let texture = gpu.create_texture( - TextureCreateInfo::new() - .with_format(TextureFormat::R8g8b8a8Unorm) + &TextureCreateInfo::new() + .with_format(TextureFormat::R8G8B8A8_UNORM) .with_type(TextureType::_2D) .with_width(image_size.0) .with_height(image_size.1) .with_layer_count_or_depth(1) .with_num_levels(1) - .with_usage(TextureUsage::Sampler), + .with_usage(TextureUsage::SAMPLER), )?; - let transfer_buffer = gpu + let mut transfer_buffer = gpu .create_transfer_buffer() .with_size(size_bytes) - .with_usage(TransferBufferUsage::Upload) + .with_usage(TransferBufferUsage::UPLOAD) .build()?; - let mut buffer_mem = transfer_buffer.map::(gpu, false); - image.with_lock(|image_bytes| { - buffer_mem.mem_mut().copy_from_slice(image_bytes); - }); - buffer_mem.unmap(); + transfer_buffer.mapped_mut(false, |bytes| { + image.with_lock(|image_bytes| unsafe { + std::ptr::copy_nonoverlapping::( + image_bytes.as_ptr(), + bytes.as_mut_ptr(), + image_bytes.len(), + ); + }) + })?; copy_pass.upload_to_gpu_texture( TextureTransferInfo::new() @@ -472,13 +489,13 @@ fn create_texture_from_image( } /// Creates a GPU buffer and uploads data to it using the given `copy_pass` and `transfer_buffer`. -fn create_buffer_with_data( - gpu: &Device, - transfer_buffer: &TransferBuffer, +fn create_buffer_with_data<'gpu, T: Copy>( + gpu: &'gpu OwnedDevice, + transfer_buffer: &mut Owned<'_, TransferBuffer>, copy_pass: &CopyPass, usage: BufferUsageFlags, data: &[T], -) -> Result { +) -> Result, Error> { // Figure out the length of the data in bytes let len_bytes = data.len() * std::mem::size_of::(); @@ -494,26 +511,20 @@ fn create_buffer_with_data( // Note: We set `cycle` to true since we're reusing the same transfer buffer to // initialize both the vertex and index buffer. This makes SDL synchronize the transfers // so that one doesn't interfere with the other. - let mut map = transfer_buffer.map::(gpu, true); - let mem = map.mem_mut(); - for (index, &value) in data.iter().enumerate() { - mem[index] = value; - } - - // Now unmap the memory since we're done copying - map.unmap(); + transfer_buffer.mapped_mut(true, |bytes| unsafe { + std::ptr::copy_nonoverlapping::( + data.as_ptr() as *const u8, + bytes.as_mut_ptr(), + len_bytes, + ); + })?; // Finally, add a command to the copy pass to upload this data to the GPU // // Note: We also set `cycle` to true here for the same reason. copy_pass.upload_to_gpu_buffer( - TransferBufferLocation::new() - .with_offset(0) - .with_transfer_buffer(transfer_buffer), - BufferRegion::new() - .with_offset(0) - .with_size(len_bytes as u32) - .with_buffer(&buffer), + transfer_buffer.get(0..), + buffer.get(0..len_bytes as u32), true, ); diff --git a/examples/gpu-triangle.rs b/examples/gpu-triangle.rs index ca1dd6f3..6dc43420 100644 --- a/examples/gpu-triangle.rs +++ b/examples/gpu-triangle.rs @@ -3,8 +3,8 @@ extern crate sdl3; use sdl3::{ event::Event, gpu::{ - ColorTargetDescription, ColorTargetInfo, Device, FillMode, GraphicsPipelineTargetInfo, - LoadOp, PrimitiveType, ShaderFormat, ShaderStage, StoreOp, + ColorTargetDescription, ColorTargetInfo, FillMode, GraphicsPipelineTargetInfo, LoadOp, + OwnedDevice, PrimitiveType, ShaderFormat, ShaderStage, StoreOp, }, keyboard::Keycode, pixels::Color, @@ -24,11 +24,11 @@ pub fn main() -> Result<(), Box> { // by default, and we specify that our shaders will be SPIR-V ones (even through we // aren't using any shaders) // We'll also turn on debug mode to true, so we get debug stuff - let gpu = Device::new( - ShaderFormat::SpirV | ShaderFormat::Dxil | ShaderFormat::Dxbc | ShaderFormat::MetalLib, + let gpu = OwnedDevice::new( + ShaderFormat::SPIRV | ShaderFormat::DXIL | ShaderFormat::DXBC | ShaderFormat::METALLIB, true, - )? - .with_window(&window)?; + )?; + gpu.claim_window(&window)?; let fs_source = include_bytes!("shaders/triangle.frag.spv"); let vs_source = include_bytes!("shaders/triangle.vert.spv"); @@ -36,13 +36,13 @@ pub fn main() -> Result<(), Box> { // Our shaders, require to be precompiled by a SPIR-V compiler beforehand let vs_shader = gpu .create_shader() - .with_code(ShaderFormat::SpirV, vs_source, ShaderStage::Vertex) + .with_code(ShaderFormat::SPIRV, vs_source, ShaderStage::Vertex) .with_entrypoint(c"main") .build()?; let fs_shader = gpu .create_shader() - .with_code(ShaderFormat::SpirV, fs_source, ShaderStage::Fragment) + .with_code(ShaderFormat::SPIRV, fs_source, ShaderStage::Fragment) .with_entrypoint(c"main") .build()?; @@ -95,16 +95,18 @@ pub fn main() -> Result<(), Box> { let color_targets = [ ColorTargetInfo::default() .with_texture(&swapchain) - .with_load_op(LoadOp::Clear) - .with_store_op(StoreOp::Store) + .with_load_op(LoadOp::CLEAR) + .with_store_op(StoreOp::STORE) .with_clear_color(Color::RGB(5, 3, 255)), //blue with small RG bias ]; - let render_pass = gpu.begin_render_pass(&command_buffer, &color_targets, None)?; - render_pass.bind_graphics_pipeline(&pipeline); - // Screen is cleared here due to the color target info - // Now we'll draw the triangle primitives - render_pass.draw_primitives(3, 1, 0, 0); - gpu.end_render_pass(render_pass); + + command_buffer.render_pass(&color_targets, None, |_cmd, render_pass| { + render_pass.bind_graphics_pipeline(&pipeline); + // Screen is cleared here due to the color target info + // Now we'll draw the triangle primitives + render_pass.draw_primitives(3, 1, 0, 0); + })?; + command_buffer.submit()?; } else { // Swapchain unavailable, cancel work diff --git a/src/sdl3/audio.rs b/src/sdl3/audio.rs index 2a0fa34d..f97ec14f 100644 --- a/src/sdl3/audio.rs +++ b/src/sdl3/audio.rs @@ -1012,6 +1012,7 @@ pub struct AudioStreamOwner { #[expect(dead_code)] audio_subsystem: Option, } +unsafe impl Send for AudioStreamOwner {} pub struct AudioStream { stream: *mut sys::audio::SDL_AudioStream, diff --git a/src/sdl3/event.rs b/src/sdl3/event.rs index 2873be66..a0caa5cd 100644 --- a/src/sdl3/event.rs +++ b/src/sdl3/event.rs @@ -1005,7 +1005,7 @@ unsafe impl Sync for Event {} // This would honestly be nice if it took &self instead of self, // but Event::User's raw pointers kind of removes that possibility. impl Event { - fn to_ll(&self) -> Option { + pub fn to_ll(&self) -> Option { let mut ret = mem::MaybeUninit::uninit(); match *self { Event::User { diff --git a/src/sdl3/gpu/abstraction.rs b/src/sdl3/gpu/abstraction.rs new file mode 100644 index 00000000..e6e39cc4 --- /dev/null +++ b/src/sdl3/gpu/abstraction.rs @@ -0,0 +1,10 @@ +use std::marker::PhantomData; + +use crate::gpu::Buffer; + +// a typed pseudo-reference to an object located in a `Buffer` on the GPU +pub struct Ref<'a, T> { + pub(crate) buf: &'a Buffer, + pub(crate) offset: u32, + pub(crate) marker: PhantomData<&'a T>, +} diff --git a/src/sdl3/gpu/auto_trait.rs b/src/sdl3/gpu/auto_trait.rs new file mode 100644 index 00000000..54b6cbad --- /dev/null +++ b/src/sdl3/gpu/auto_trait.rs @@ -0,0 +1,48 @@ +// manually checking auto-traits for a type +type _X = u32; +const _: () = _copy::<_X>(); +const _: () = _send::<_X>(); +const _: () = _sync::<_X>(); +const _: () = _unpin::<_X>(); + +const fn _copy() {} +const fn _send() {} +const fn _sync() {} +const fn _unpin() {} + +#[cfg(test)] +mod assertions { + use static_assertions::{assert_impl_all, assert_not_impl_any}; + + macro_rules! thread_safe { + ($t:ty) => { + assert_impl_all!($t: Sync); + assert_not_impl_any!($t: Copy, Send, Unpin); + }; + } + macro_rules! thread_unsafe { + ($t:ty) => { + assert_not_impl_any!($t: Copy, Sync, Send, Unpin); + }; + } + + use crate::gpu::*; + + // definitely not thread-safe + thread_unsafe!(CommandBuffer); + + // at least SDL_GetGPUDeviceProperties is documented as + // "safe to call from any thread", which implies that the device can be shared + thread_safe!(Device); + + // these are ambiguous + thread_safe!(Buffer); + thread_safe!(Texture); + + // possibly thread-safe, but haven't checked yet + thread_unsafe!(Sampler); + thread_unsafe!(TransferBuffer); + thread_unsafe!(GraphicsPipeline); + thread_unsafe!(ComputePipeline); + thread_unsafe!(Shader); +} diff --git a/src/sdl3/gpu/buffer.rs b/src/sdl3/gpu/buffer.rs deleted file mode 100644 index 0fb37a0b..00000000 --- a/src/sdl3/gpu/buffer.rs +++ /dev/null @@ -1,294 +0,0 @@ -use crate::{ - get_error, - gpu::{device::WeakDevice, BufferUsageFlags, Device, TransferBufferUsage, VertexInputRate}, - sys, Error, -}; -use std::sync::Arc; -use sys::gpu::{ - SDL_CreateGPUBuffer, SDL_CreateGPUTransferBuffer, SDL_GPUBuffer, SDL_GPUBufferBinding, - SDL_GPUBufferCreateInfo, SDL_GPUBufferRegion, SDL_GPUTransferBuffer, - SDL_GPUTransferBufferCreateInfo, SDL_GPUTransferBufferLocation, SDL_GPUTransferBufferUsage, - SDL_GPUVertexBufferDescription, SDL_GPUVertexInputRate, SDL_MapGPUTransferBuffer, - SDL_ReleaseGPUBuffer, SDL_ReleaseGPUTransferBuffer, SDL_UnmapGPUTransferBuffer, -}; - -#[repr(C)] -#[derive(Default)] -pub struct BufferBinding { - pub(super) inner: SDL_GPUBufferBinding, -} -impl BufferBinding { - pub fn new() -> Self { - Default::default() - } - - pub fn with_buffer(mut self, buffer: &Buffer) -> Self { - self.inner.buffer = buffer.raw(); - self - } - - pub fn with_offset(mut self, offset: u32) -> Self { - self.inner.offset = offset; - self - } -} - -#[derive(Default)] -pub struct TransferBufferLocation { - pub(super) inner: SDL_GPUTransferBufferLocation, -} -impl TransferBufferLocation { - pub fn new() -> Self { - Default::default() - } - - pub fn with_transfer_buffer(mut self, transfer_buffer: &TransferBuffer) -> Self { - self.inner.transfer_buffer = transfer_buffer.raw(); - self - } - - pub fn with_offset(mut self, offset: u32) -> Self { - self.inner.offset = offset; - self - } -} - -#[derive(Default)] -pub struct BufferRegion { - pub(super) inner: SDL_GPUBufferRegion, -} -impl BufferRegion { - pub fn new() -> Self { - Default::default() - } - - pub fn with_buffer(mut self, buffer: &Buffer) -> Self { - self.inner.buffer = buffer.raw(); - self - } - - pub fn with_offset(mut self, offset: u32) -> Self { - self.inner.offset = offset; - self - } - - pub fn with_size(mut self, size: u32) -> Self { - self.inner.size = size; - self - } -} - -#[repr(C)] -#[derive(Clone, Default)] -pub struct VertexBufferDescription { - inner: SDL_GPUVertexBufferDescription, -} -impl VertexBufferDescription { - pub fn new() -> Self { - Default::default() - } - - pub fn with_slot(mut self, value: u32) -> Self { - self.inner.slot = value; - self - } - - pub fn with_pitch(mut self, value: u32) -> Self { - self.inner.pitch = value; - self - } - - pub fn with_input_rate(mut self, value: VertexInputRate) -> Self { - self.inner.input_rate = SDL_GPUVertexInputRate(value as i32); - self - } - - pub fn with_instance_step_rate(mut self, value: u32) -> Self { - self.inner.instance_step_rate = value; - self - } -} - -/// Manages the raw `SDL_GPUBuffer` pointer and releases it on drop -struct BufferContainer { - raw: *mut SDL_GPUBuffer, - device: WeakDevice, -} -impl Drop for BufferContainer { - #[doc(alias = "SDL_ReleaseGPUBuffer")] - fn drop(&mut self) { - if let Some(device) = self.device.upgrade() { - unsafe { - SDL_ReleaseGPUBuffer(device.raw(), self.raw); - } - } - } -} - -#[doc(alias = "SDL_GPUBuffer")] -#[derive(Clone)] -pub struct Buffer { - inner: Arc, - len: u32, -} -impl Buffer { - /// Yields the raw SDL_GPUBuffer pointer. - #[inline] - pub fn raw(&self) -> *mut SDL_GPUBuffer { - self.inner.raw - } - - /// The length of this buffer in bytes. - pub fn len(&self) -> u32 { - self.len - } -} - -pub struct BufferBuilder<'a> { - device: &'a Device, - inner: SDL_GPUBufferCreateInfo, -} -impl<'a> BufferBuilder<'a> { - pub(super) fn new(device: &'a Device) -> Self { - Self { - device, - inner: Default::default(), - } - } - - pub fn with_usage(mut self, value: BufferUsageFlags) -> Self { - self.inner.usage = value as u32; - self - } - - pub fn with_size(mut self, value: u32) -> Self { - self.inner.size = value; - self - } - - pub fn build(self) -> Result { - let raw_buffer = unsafe { SDL_CreateGPUBuffer(self.device.raw(), &self.inner) }; - if raw_buffer.is_null() { - Err(get_error()) - } else { - Ok(Buffer { - len: self.inner.size, - inner: Arc::new(BufferContainer { - raw: raw_buffer, - device: self.device.weak(), - }), - }) - } - } -} - -/// Mapped memory for a transfer buffer. -pub struct BufferMemMap<'a, T> { - device: &'a Device, - transfer_buffer: &'a TransferBuffer, - mem: *mut T, -} - -impl<'a, T> BufferMemMap<'a, T> -where - T: Copy, -{ - /// Access the memory as a readonly slice. - pub fn mem(&self) -> &[T] { - let count = self.transfer_buffer.len() as usize / std::mem::size_of::(); - unsafe { std::slice::from_raw_parts(self.mem, count) } - } - - /// Access the memory as a mutable slice. - pub fn mem_mut(&mut self) -> &mut [T] { - let count = self.transfer_buffer.len() as usize / std::mem::size_of::(); - unsafe { std::slice::from_raw_parts_mut(self.mem, count) } - } - - #[doc(alias = "SDL_UnmapGPUTransferBuffer")] - pub fn unmap(self) { - unsafe { SDL_UnmapGPUTransferBuffer(self.device.raw(), self.transfer_buffer.raw()) }; - } -} - -/// Manages the raw `SDL_GPUTransferBuffer` pointer and releases it on drop -struct TransferBufferContainer { - raw: *mut SDL_GPUTransferBuffer, - device: WeakDevice, -} -impl Drop for TransferBufferContainer { - #[doc(alias = "SDL_ReleaseGPUTransferBuffer")] - fn drop(&mut self) { - if let Some(device) = self.device.upgrade() { - unsafe { - SDL_ReleaseGPUTransferBuffer(device.raw(), self.raw); - } - } - } -} - -#[derive(Clone)] -pub struct TransferBuffer { - inner: Arc, - len: u32, -} -impl TransferBuffer { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUTransferBuffer { - self.inner.raw - } - - #[doc(alias = "SDL_MapGPUTransferBuffer")] - pub fn map<'a, T: Copy>(&'a self, device: &'a Device, cycle: bool) -> BufferMemMap<'a, T> { - BufferMemMap { - device, - transfer_buffer: self, - mem: unsafe { SDL_MapGPUTransferBuffer(device.raw(), self.raw(), cycle) } as *mut T, - } - } - - /// The length of this buffer in bytes. - pub fn len(&self) -> u32 { - self.len - } -} - -pub struct TransferBufferBuilder<'a> { - device: &'a Device, - inner: SDL_GPUTransferBufferCreateInfo, -} -impl<'a> TransferBufferBuilder<'a> { - pub(super) fn new(device: &'a Device) -> Self { - Self { - device, - inner: Default::default(), - } - } - - /// How the buffer will be used. - pub fn with_usage(mut self, value: TransferBufferUsage) -> Self { - self.inner.usage = SDL_GPUTransferBufferUsage(value as i32); - self - } - - /// Desired size of the buffer in bytes. - pub fn with_size(mut self, value: u32) -> Self { - self.inner.size = value; - self - } - - pub fn build(self) -> Result { - let raw_buffer = unsafe { SDL_CreateGPUTransferBuffer(self.device.raw(), &self.inner) }; - if raw_buffer.is_null() { - Err(get_error()) - } else { - Ok(TransferBuffer { - inner: Arc::new(TransferBufferContainer { - raw: raw_buffer, - device: self.device.weak(), - }), - len: self.inner.size, - }) - } - } -} diff --git a/src/sdl3/gpu/command_buffer/compute.rs b/src/sdl3/gpu/command_buffer/compute.rs new file mode 100644 index 00000000..787d6439 --- /dev/null +++ b/src/sdl3/gpu/command_buffer/compute.rs @@ -0,0 +1,67 @@ +use sys::gpu::{ + SDL_BindGPUComputePipeline, SDL_BindGPUComputeSamplers, SDL_BindGPUComputeStorageBuffers, + SDL_BindGPUComputeStorageTextures, SDL_DispatchGPUCompute, SDL_DispatchGPUComputeIndirect, + SDL_GPUComputePass, +}; + +use crate::gpu::{ + info_struct::IndirectDispatchCommand, Buffer, ComputePipeline, Extern, Texture, + TextureSamplerBinding, +}; + +pub type ComputePass = Extern; + +impl ComputePass { + #[doc(alias = "SDL_BindGPUComputePipeline")] + pub fn bind_compute_pipeline(&self, pipeline: &ComputePipeline) { + unsafe { SDL_BindGPUComputePipeline(self.ll(), pipeline.ll()) } + } + + #[doc(alias = "SDL_BindGPUComputeSamplers")] + pub fn bind_compute_samplers(&self, first_slot: u32, samplers: &[&TextureSamplerBinding]) { + unsafe { + SDL_BindGPUComputeSamplers( + self.ll(), + first_slot, + samplers.as_ptr().cast(), + samplers.len() as u32, + ) + } + } + + #[doc(alias = "SDL_BindGPUComputeStorageBuffers")] + pub fn bind_compute_storage_buffers(&self, first_slot: u32, storage_buffers: &[&Buffer]) { + unsafe { + SDL_BindGPUComputeStorageBuffers( + self.ll(), + first_slot, + storage_buffers.as_ptr().cast(), + storage_buffers.len() as u32, + ) + } + } + + #[doc(alias = "SDL_BindGPUComputeStorageTextures")] + pub fn bind_compute_storage_textures(&self, first_slot: u32, storage_textures: &[&Texture]) { + unsafe { + SDL_BindGPUComputeStorageTextures( + self.ll(), + first_slot, + storage_textures.as_ptr().cast(), + storage_textures.len() as u32, + ) + } + } + + /// Dispatch compute work + #[doc(alias = "SDL_DispatchGPUCompute")] + pub fn dispatch(&self, groupcount_x: u32, groupcount_y: u32, groupcount_z: u32) { + unsafe { SDL_DispatchGPUCompute(self.ll(), groupcount_x, groupcount_y, groupcount_z) } + } + + /// Dispatch compute work. Same as `dispatch`, except the dispatch parameters are read from GPU memory. + #[doc(alias = "SDL_DispatchGPUComputeIndirect")] + pub fn dispatch_indirect(&self, dispatch: crate::gpu::Ref<'_, IndirectDispatchCommand>) { + unsafe { SDL_DispatchGPUComputeIndirect(self.ll(), dispatch.buf.ll(), dispatch.offset) } + } +} diff --git a/src/sdl3/gpu/command_buffer/copy.rs b/src/sdl3/gpu/command_buffer/copy.rs new file mode 100644 index 00000000..1680d143 --- /dev/null +++ b/src/sdl3/gpu/command_buffer/copy.rs @@ -0,0 +1,71 @@ +use crate::gpu::{ + info_struct::{BufferLocation, TextureLocation}, + BufferRegion, Extern, TextureRegion, TextureTransferInfo, TransferBufferLocation, +}; + +use sys::gpu::{ + SDL_CopyGPUBufferToBuffer, SDL_CopyGPUTextureToTexture, SDL_DownloadFromGPUBuffer, + SDL_DownloadFromGPUTexture, SDL_GPUCopyPass, SDL_UploadToGPUBuffer, SDL_UploadToGPUTexture, +}; + +pub type CopyPass = Extern; + +impl CopyPass { + #[doc(alias = "SDL_UploadToGPUBuffer")] + pub fn upload_to_gpu_buffer( + &self, + transfer_buf_location: TransferBufferLocation, + buffer_region: BufferRegion, + cycle: bool, + ) { + unsafe { + SDL_UploadToGPUBuffer( + self.ll(), + &transfer_buf_location.inner, + &buffer_region.inner, + cycle, + ) + } + } + + #[doc(alias = "SDL_UploadToGPUTexture")] + pub fn upload_to_gpu_texture(&self, src: TextureTransferInfo, dst: TextureRegion, cycle: bool) { + unsafe { SDL_UploadToGPUTexture(self.ll(), &src.inner, &dst.inner, cycle) } + } + + #[doc(alias = "SDL_CopyGPUTextureToTexture")] + pub fn copy_texture_to_texture( + &self, + src: TextureLocation<'_>, + dst: TextureLocation<'_>, + w: u32, + h: u32, + d: u32, + cycle: bool, + ) { + unsafe { SDL_CopyGPUTextureToTexture(self.ll(), &src.inner, &dst.inner, w, h, d, cycle) } + } + + #[doc(alias = "SDL_CopyGPUBufferToBuffer")] + pub fn copy_buffer_to_buffer( + &self, + src: BufferLocation<'_>, + dst: BufferLocation<'_>, + size: u32, + cycle: bool, + ) { + unsafe { SDL_CopyGPUBufferToBuffer(self.ll(), &src.inner, &dst.inner, size, cycle) } + } + + /// Note: The data is not guaranteed to be copied until the command buffer fence is signaled. + #[doc(alias = "SDL_DownloadFromGPUBuffer")] + pub fn download_from_gpu_buffer(&self, src: BufferRegion<'_>, dst: TransferBufferLocation<'_>) { + unsafe { SDL_DownloadFromGPUBuffer(self.ll(), &src.inner, &dst.inner) } + } + + /// Note: The data is not guaranteed to be copied until the command buffer fence is signaled. + #[doc(alias = "SDL_DownloadFromGPUTexture")] + pub fn download_from_gpu_texture(&self, src: TextureRegion<'_>, dst: TextureTransferInfo<'_>) { + unsafe { SDL_DownloadFromGPUTexture(self.ll(), &src.inner, &dst.inner) } + } +} diff --git a/src/sdl3/gpu/command_buffer/fence.rs b/src/sdl3/gpu/command_buffer/fence.rs new file mode 100644 index 00000000..d188e52e --- /dev/null +++ b/src/sdl3/gpu/command_buffer/fence.rs @@ -0,0 +1,14 @@ +use sys::gpu::SDL_GPUFence; + +use crate::gpu::{resource::GpuRelease, Extern}; + +pub type Fence = Extern; + +unsafe impl GpuRelease for Fence { + type SDLType = SDL_GPUFence; + + const RELEASE: unsafe extern "C" fn(*mut sys::gpu::SDL_GPUDevice, *mut Self::SDLType) = + sys::gpu::SDL_ReleaseGPUFence; + + type ExtraState = (); +} diff --git a/src/sdl3/gpu/command_buffer/mod.rs b/src/sdl3/gpu/command_buffer/mod.rs new file mode 100644 index 00000000..c84d167c --- /dev/null +++ b/src/sdl3/gpu/command_buffer/mod.rs @@ -0,0 +1,269 @@ +use std::{ + marker::PhantomData, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +use crate::{get_error, gpu::util::nonnull_ext_or_get_error, Error}; + +use super::{ + util::Defer, ColorTargetInfo, DepthStencilTargetInfo, Device, Extern, + StorageBufferReadWriteBinding, StorageTextureReadWriteBinding, Texture, +}; + +use sys::gpu::{ + SDL_AcquireGPUCommandBuffer, SDL_AcquireGPUSwapchainTexture, SDL_BeginGPUComputePass, + SDL_BeginGPUCopyPass, SDL_BeginGPURenderPass, SDL_GPUColorTargetInfo, SDL_GPUCommandBuffer, + SDL_GPUDevice, SDL_PushGPUComputeUniformData, SDL_PushGPUFragmentUniformData, + SDL_PushGPUVertexUniformData, SDL_WaitAndAcquireGPUSwapchainTexture, +}; + +mod compute; +pub use compute::ComputePass; + +mod render; +pub use render::RenderPass; + +mod copy; +pub use copy::CopyPass; + +mod swapchain; +pub use swapchain::SwapchainTexture; + +mod fence; +pub use fence::Fence; + +pub type CommandBuffer = Extern; + +#[repr(transparent)] +pub struct OwnedCommandBuffer<'gpu> { + pub(super) raw: NonNull>, + pub(super) _marker: std::marker::PhantomData<&'gpu SDL_GPUDevice>, +} + +impl<'gpu> Deref for OwnedCommandBuffer<'gpu> { + type Target = CommandBuffer; + + fn deref(&self) -> &Self::Target { + unsafe { self.raw.as_ref() } + } +} + +impl<'gpu> DerefMut for OwnedCommandBuffer<'gpu> { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.raw.as_mut() } + } +} +impl Device { + #[doc(alias = "SDL_AcquireGPUCommandBuffer")] + pub fn acquire_command_buffer<'gpu>(&'gpu self) -> Result, Error> { + let raw = nonnull_ext_or_get_error(unsafe { SDL_AcquireGPUCommandBuffer(self.ll()) })?; + Ok(OwnedCommandBuffer { + raw, + _marker: PhantomData, + }) + } +} + +impl<'gpu> Drop for OwnedCommandBuffer<'gpu> { + fn drop(&mut self) { + if std::thread::panicking() { + // if already panicking, let's not make it worse + return; + } else { + panic!( + "A command buffer was implicitly dropped, + but should be explicitly submitted or cancelled." + ); + } + } +} + +impl<'gpu> OwnedCommandBuffer<'gpu> { + #[doc(alias = "SDL_SubmitGPUCommandBuffer")] + pub fn submit(self) -> Result<(), Error> { + let raw = self.ll(); + std::mem::forget(self); + + if unsafe { sys::gpu::SDL_SubmitGPUCommandBuffer(raw) } { + Ok(()) + } else { + Err(get_error()) + } + } + + #[doc(alias = "SDL_CancelGPUCommandBuffer")] + pub fn cancel(self) { + let raw = self.ll(); + std::mem::forget(self); + + unsafe { + sys::gpu::SDL_CancelGPUCommandBuffer(raw); + } + } +} + +impl CommandBuffer { + /// Run a compute pass on this command buffer. + /// + /// Note that *writeable* resources are bound at the start of the pass for the whole pass, + /// whereas readonly resources are bound separately and can be rebound during the pass. + #[doc(alias = "SDL_BeginGPUComputePass")] + pub fn compute_pass( + &mut self, + storage_texture_bindings: &[StorageTextureReadWriteBinding], + storage_buffer_bindings: &[StorageBufferReadWriteBinding], + func: impl for<'a> FnOnce(&'a Extern, &'a mut ComputePass) -> R, + ) -> Result { + let mut raw = nonnull_ext_or_get_error(unsafe { + SDL_BeginGPUComputePass( + self.ll(), + storage_texture_bindings.as_ptr().cast(), + storage_texture_bindings.len() as u32, + storage_buffer_bindings.as_ptr().cast(), + storage_buffer_bindings.len() as u32, + ) + })?; + + let _defer = Defer::new(move || unsafe { + sys::gpu::SDL_EndGPUComputePass(raw.as_ptr().cast()); + }); + + Ok(unsafe { func(self, raw.as_mut()) }) + } + + /// Run a render pass on this command buffer. + #[doc(alias = "SDL_BeginGPURenderPass")] + pub fn render_pass( + &mut self, + color_info: &[ColorTargetInfo], + depth_stencil_target: Option<&DepthStencilTargetInfo>, + func: impl for<'a> FnOnce(&'a CommandBuffer, &'a mut RenderPass) -> R, + ) -> Result { + let mut raw = nonnull_ext_or_get_error(unsafe { + SDL_BeginGPURenderPass( + self.ll(), + color_info.as_ptr() as *const SDL_GPUColorTargetInfo, + color_info.len() as u32, + depth_stencil_target + .map(std::ptr::from_ref) + .unwrap_or(std::ptr::null()) + .cast(), + ) + })?; + + let _defer = Defer::new(move || unsafe { + sys::gpu::SDL_EndGPURenderPass(raw.as_ptr().cast()); + }); + + Ok(unsafe { func(self, raw.as_mut()) }) + } + + /// Run a copy pass on this command buffer. + #[doc(alias = "SDL_BeginGPUCopyPass")] + pub fn copy_pass( + &mut self, + func: impl for<'a> FnOnce(&'a CommandBuffer, &'a mut CopyPass) -> R, + ) -> Result { + let mut raw = nonnull_ext_or_get_error(unsafe { SDL_BeginGPUCopyPass(self.ll()) })?; + + let _defer = Defer::new(move || unsafe { + sys::gpu::SDL_EndGPUCopyPass(raw.as_ptr().cast()); + }); + + Ok(unsafe { func(self, raw.as_mut()) }) + } + + // FIXME: + // The lifetime here isn't quite right. + // The swapchain texture can only be used with the command buffer it + // was obtained from, but we also can't borrow the command buffer here + // without preventing you from running passes. + #[doc(alias = "SDL_WaitAndAcquireGPUSwapchainTexture")] + pub fn wait_and_acquire_swapchain_texture<'a>( + &mut self, + w: &'a crate::video::Window, + ) -> Result, Option> { + let mut raw = std::ptr::null_mut(); + let mut width = 0; + let mut height = 0; + let success = unsafe { + SDL_WaitAndAcquireGPUSwapchainTexture( + self.ll(), + w.raw(), + &mut raw, + &mut width, + &mut height, + ) + }; + let raw: *mut Texture = raw.cast(); + if success { + if let Some(tex) = unsafe { raw.as_ref() } { + Ok(SwapchainTexture { tex, width, height }) + } else { + Err(None) + } + } else { + Err(Some(get_error())) + } + } + + #[doc(alias = "SDL_AcquireGPUSwapchainTexture")] + pub fn acquire_swapchain_texture<'a>( + &mut self, + w: &'a crate::video::Window, + ) -> Result, Option> { + let mut raw = std::ptr::null_mut(); + let mut width = 0; + let mut height = 0; + let success = unsafe { + SDL_AcquireGPUSwapchainTexture(self.ll(), w.raw(), &mut raw, &mut width, &mut height) + }; + let raw: *mut Texture = raw.cast(); + if success { + if let Some(tex) = unsafe { raw.as_ref() } { + Ok(SwapchainTexture { tex, width, height }) + } else { + Err(None) + } + } else { + Err(Some(get_error())) + } + } + + #[doc(alias = "SDL_PushGPUVertexUniformData")] + pub fn push_vertex_uniform_data(&self, slot_index: u32, data: &T) { + unsafe { + SDL_PushGPUVertexUniformData( + self.ll(), + slot_index, + (data as *const T) as *const std::ffi::c_void, + size_of::() as u32, + ) + } + } + + #[doc(alias = "SDL_PushGPUFragmentUniformData")] + pub fn push_fragment_uniform_data(&self, slot_index: u32, data: &T) { + unsafe { + SDL_PushGPUFragmentUniformData( + self.ll(), + slot_index, + (data as *const T) as *const std::ffi::c_void, + size_of::() as u32, + ) + } + } + + #[doc(alias = "SDL_PushGPUComputeUniformData")] + pub fn push_compute_uniform_data(&self, slot_index: u32, data: &T) { + unsafe { + SDL_PushGPUComputeUniformData( + self.ll(), + slot_index, + (data as *const T) as *const std::ffi::c_void, + size_of::() as u32, + ) + } + } +} diff --git a/src/sdl3/gpu/command_buffer/render.rs b/src/sdl3/gpu/command_buffer/render.rs new file mode 100644 index 00000000..3d1622c5 --- /dev/null +++ b/src/sdl3/gpu/command_buffer/render.rs @@ -0,0 +1,152 @@ +use crate::gpu::{ + Buffer, BufferBinding, Extern, GraphicsPipeline, IndexElementSize, Texture, + TextureSamplerBinding, +}; + +use sys::gpu::{ + SDL_BindGPUFragmentSamplers, SDL_BindGPUIndexBuffer, SDL_BindGPUVertexBuffers, + SDL_DrawGPUIndexedPrimitives, SDL_GPUBufferBinding, SDL_GPURenderPass, + SDL_GPUTextureSamplerBinding, SDL_GPUViewport, SDL_SetGPUViewport, +}; + +pub type RenderPass = Extern; + +impl RenderPass { + #[doc(alias = "SDL_SetGPUViewport")] + pub fn set_viewport(&mut self, viewport: &SDL_GPUViewport) { + unsafe { SDL_SetGPUViewport(self.ll(), viewport) } + } + + #[doc(alias = "SDL_BindGPUGraphicsPipeline")] + pub fn bind_graphics_pipeline(&self, pipeline: &GraphicsPipeline) { + unsafe { sys::gpu::SDL_BindGPUGraphicsPipeline(self.ll(), pipeline.ll()) } + } + + #[doc(alias = "SDL_BindGPUVertexBuffers")] + pub fn bind_vertex_buffers(&self, first_slot: u32, bindings: &[BufferBinding]) { + unsafe { + SDL_BindGPUVertexBuffers( + self.ll(), + first_slot, + bindings.as_ptr() as *mut SDL_GPUBufferBinding, + bindings.len() as u32, + ) + } + } + #[doc(alias = "SDL_BindGPUFragmentSamplers")] + pub fn bind_vertex_samplers(&self, first_slot: u32, bindings: &[&TextureSamplerBinding]) { + unsafe { + SDL_BindGPUFragmentSamplers( + self.ll(), + first_slot, + bindings.as_ptr() as *const SDL_GPUTextureSamplerBinding, + bindings.len() as u32, + ); + } + } + + #[doc(alias = "SDL_BindGPUVertexStorageBuffers")] + pub fn bind_vertex_storage_buffers(&self, first_slot: u32, storage_buffers: &[&Buffer]) { + unsafe { + sys::gpu::SDL_BindGPUVertexStorageBuffers( + self.ll(), + first_slot, + storage_buffers.as_ptr().cast(), + storage_buffers.len() as u32, + ) + } + } + + #[doc(alias = "SDL_BindGPUVertexStorageTextures")] + pub fn bind_vertex_storage_textures(&self, first_slot: u32, storage_textures: &[&Texture]) { + unsafe { + sys::gpu::SDL_BindGPUVertexStorageTextures( + self.ll(), + first_slot, + storage_textures.as_ptr().cast(), + storage_textures.len() as u32, + ) + } + } + + #[doc(alias = "SDL_BindGPUIndexBuffer")] + pub fn bind_index_buffer(&self, binding: &BufferBinding, index_element_size: IndexElementSize) { + unsafe { SDL_BindGPUIndexBuffer(self.ll(), &binding.inner, index_element_size) } + } + + #[doc(alias = "SDL_BindGPUFragmentSamplers")] + pub fn bind_fragment_samplers(&self, first_slot: u32, bindings: &[TextureSamplerBinding]) { + unsafe { + SDL_BindGPUFragmentSamplers( + self.ll(), + first_slot, + bindings.as_ptr() as *const SDL_GPUTextureSamplerBinding, + bindings.len() as u32, + ); + } + } + + #[doc(alias = "SDL_BindGPUFragmentStorageBuffers")] + pub fn bind_fragment_storage_buffers(&self, first_slot: u32, storage_buffers: &[&Buffer]) { + unsafe { + sys::gpu::SDL_BindGPUFragmentStorageBuffers( + self.ll(), + first_slot, + storage_buffers.as_ptr().cast(), + storage_buffers.len() as u32, + ) + } + } + + #[doc(alias = "SDL_BindGPUFragmentStorageTextures")] + pub fn bind_fragment_storage_textures(&self, first_slot: u32, storage_textures: &[&Texture]) { + unsafe { + sys::gpu::SDL_BindGPUFragmentStorageTextures( + self.ll(), + first_slot, + storage_textures.as_ptr().cast(), + storage_textures.len() as u32, + ) + } + } + + #[doc(alias = "SDL_DrawGPUIndexedPrimitives")] + pub fn draw_indexed_primitives( + &self, + num_indices: u32, + num_instances: u32, + first_index: u32, + vertex_offset: i32, + first_instance: u32, + ) { + unsafe { + SDL_DrawGPUIndexedPrimitives( + self.ll(), + num_indices, + num_instances, + first_index, + vertex_offset, + first_instance, + ); + } + } + + #[doc(alias = "SDL_DrawGPUPrimitives")] + pub fn draw_primitives( + &self, + num_vertices: u32, + num_instances: u32, + first_vertex: u32, + first_instance: u32, + ) { + unsafe { + sys::gpu::SDL_DrawGPUPrimitives( + self.ll(), + num_vertices, + num_instances, + first_vertex, + first_instance, + ); + } + } +} diff --git a/src/sdl3/gpu/command_buffer/swapchain.rs b/src/sdl3/gpu/command_buffer/swapchain.rs new file mode 100644 index 00000000..adf6f7c7 --- /dev/null +++ b/src/sdl3/gpu/command_buffer/swapchain.rs @@ -0,0 +1,28 @@ +use std::ops::Deref; + +use crate::gpu::Texture; + +#[doc(alias = "SDL_Texture")] +pub struct SwapchainTexture<'a> { + pub(super) tex: &'a Texture, + pub(super) width: u32, + pub(super) height: u32, +} + +impl<'a> Deref for SwapchainTexture<'a> { + type Target = Texture; + + fn deref(&self) -> &Self::Target { + &self.tex + } +} + +impl<'a> SwapchainTexture<'a> { + pub fn width(&self) -> u32 { + self.width + } + + pub fn height(&self) -> u32 { + self.height + } +} diff --git a/src/sdl3/gpu/device.rs b/src/sdl3/gpu/device.rs deleted file mode 100644 index 63384d24..00000000 --- a/src/sdl3/gpu/device.rs +++ /dev/null @@ -1,244 +0,0 @@ -use crate::{ - get_error, - gpu::{ - BufferBuilder, ColorTargetInfo, CommandBuffer, CopyPass, DepthStencilTargetInfo, - GraphicsPipelineBuilder, RenderPass, Sampler, SamplerCreateInfo, ShaderBuilder, - ShaderFormat, Texture, TextureCreateInfo, TextureFormat, TransferBufferBuilder, - }, - sys, Error, -}; -use std::sync::{Arc, Weak}; -use sys::gpu::{ - SDL_BeginGPUComputePass, SDL_BeginGPUCopyPass, SDL_BeginGPURenderPass, SDL_CreateGPUDevice, - SDL_CreateGPUSampler, SDL_CreateGPUTexture, SDL_DestroyGPUDevice, SDL_GPUColorTargetInfo, - SDL_GPUDepthStencilTargetInfo, SDL_GPUDevice, SDL_GPUViewport, - SDL_GetGPUSwapchainTextureFormat, SDL_SetGPUViewport, -}; - -use super::{ - pipeline::{StorageBufferReadWriteBinding, StorageTextureReadWriteBinding}, - ComputePass, ComputePipelineBuilder, -}; - -/// Manages the raw `SDL_GPUDevice` pointer and releases it on drop -pub(super) struct DeviceContainer(*mut SDL_GPUDevice); -impl DeviceContainer { - pub(super) fn raw(&self) -> *mut SDL_GPUDevice { - self.0 - } -} -impl Drop for DeviceContainer { - #[doc(alias = "SDL_DestroyGPUDevice")] - fn drop(&mut self) { - unsafe { SDL_DestroyGPUDevice(self.0) } - } -} - -pub(super) type WeakDevice = Weak; - -#[derive(Clone)] -pub struct Device { - inner: Arc, -} -impl Device { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUDevice { - self.inner.0 - } - - pub(super) fn weak(&self) -> WeakDevice { - Arc::downgrade(&self.inner) - } - - #[doc(alias = "SDL_CreateGPUDevice")] - pub fn new(flags: ShaderFormat, debug_mode: bool) -> Result { - let raw_device = unsafe { SDL_CreateGPUDevice(flags as u32, debug_mode, std::ptr::null()) }; - if raw_device.is_null() { - Err(get_error()) - } else { - Ok(Self { - inner: Arc::new(DeviceContainer(raw_device)), - }) - } - } - - #[doc(alias = "SDL_ClaimWindowForGPUDevice")] - pub fn with_window(self, w: &crate::video::Window) -> Result { - let p = unsafe { sys::gpu::SDL_ClaimWindowForGPUDevice(self.inner.0, w.raw()) }; - if p { - Ok(self) - } else { - Err(get_error()) - } - } - - #[doc(alias = "SDL_AcquireGPUCommandBuffer")] - pub fn acquire_command_buffer(&self) -> Result { - let raw_buffer = unsafe { sys::gpu::SDL_AcquireGPUCommandBuffer(self.inner.0) }; - if raw_buffer.is_null() { - Err(get_error()) - } else { - Ok(CommandBuffer::new(raw_buffer)) - } - } - - pub fn create_shader(&self) -> ShaderBuilder { - ShaderBuilder::new(self) - } - - #[doc(alias = "SDL_CreateGPUBuffer")] - pub fn create_buffer(&self) -> BufferBuilder { - BufferBuilder::new(self) - } - - #[doc(alias = "SDL_CreateGPUTransferBuffer")] - pub fn create_transfer_buffer(&self) -> TransferBufferBuilder { - TransferBufferBuilder::new(self) - } - - #[doc(alias = "SDL_CreateGPUSampler")] - pub fn create_sampler(&self, create_info: SamplerCreateInfo) -> Result { - let raw_sampler = unsafe { SDL_CreateGPUSampler(self.raw(), &create_info.inner) }; - if raw_sampler.is_null() { - Err(get_error()) - } else { - Ok(Sampler::new(self, raw_sampler)) - } - } - - #[doc(alias = "SDL_CreateGPUTexture")] - pub fn create_texture( - &self, - create_info: TextureCreateInfo, - ) -> Result, Error> { - let raw_texture = unsafe { SDL_CreateGPUTexture(self.raw(), &create_info.inner) }; - if raw_texture.is_null() { - Err(get_error()) - } else { - Ok(Texture::new( - self, - raw_texture, - create_info.inner.width, - create_info.inner.height, - )) - } - } - - #[doc(alias = "SDL_SetGPUViewport")] - pub fn set_viewport(&self, render_pass: &RenderPass, viewport: SDL_GPUViewport) { - unsafe { SDL_SetGPUViewport(render_pass.inner, &viewport) } - } - - pub fn get_swapchain_texture_format(&self, w: &crate::video::Window) -> TextureFormat { - unsafe { std::mem::transmute(SDL_GetGPUSwapchainTextureFormat(self.inner.0, w.raw()).0) } - } - - // You cannot begin another render pass, or begin a compute pass or copy pass until you have ended the render pass. - #[doc(alias = "SDL_BeginGPURenderPass")] - pub fn begin_render_pass( - &self, - command_buffer: &CommandBuffer, - color_info: &[ColorTargetInfo], - depth_stencil_target: Option<&DepthStencilTargetInfo>, - ) -> Result { - let p = unsafe { - SDL_BeginGPURenderPass( - command_buffer.inner, - color_info.as_ptr() as *const SDL_GPUColorTargetInfo, //heavy promise - color_info.len() as u32, - if let Some(p) = depth_stencil_target { - p as *const _ as *const SDL_GPUDepthStencilTargetInfo //heavy promise - } else { - std::ptr::null() - }, - ) - }; - if !p.is_null() { - Ok(RenderPass { inner: p }) - } else { - Err(get_error()) - } - } - - #[doc(alias = "SDL_EndGPURenderPass")] - pub fn end_render_pass(&self, pass: RenderPass) { - unsafe { - sys::gpu::SDL_EndGPURenderPass(pass.inner); - } - } - - #[doc(alias = "SDL_BeginGPUCopyPass")] - pub fn begin_copy_pass(&self, command_buffer: &CommandBuffer) -> Result { - let p = unsafe { SDL_BeginGPUCopyPass(command_buffer.inner) }; - if !p.is_null() { - Ok(CopyPass { inner: p }) - } else { - Err(get_error()) - } - } - #[doc(alias = "SDL_EndGPUCopyPass")] - pub fn end_copy_pass(&self, pass: CopyPass) { - unsafe { - sys::gpu::SDL_EndGPUCopyPass(pass.inner); - } - } - - #[doc(alias = "SDL_BeginGPUComputePass")] - pub fn begin_compute_pass( - &self, - command_buffer: &CommandBuffer, - storage_texture_bindings: &[StorageTextureReadWriteBinding], - storage_buffer_bindings: &[StorageBufferReadWriteBinding], - ) -> Result { - let p = unsafe { - SDL_BeginGPUComputePass( - command_buffer.inner, - storage_texture_bindings.as_ptr().cast(), - storage_buffer_bindings.len() as u32, - storage_buffer_bindings.as_ptr().cast(), - storage_buffer_bindings.len() as u32, - ) - }; - if !p.is_null() { - Ok(ComputePass { inner: p }) - } else { - Err(get_error()) - } - } - #[doc(alias = "SDL_EndGPUComputePass")] - pub fn end_compute_pass(&self, pass: ComputePass) { - unsafe { - sys::gpu::SDL_EndGPUComputePass(pass.inner); - } - } - - pub fn create_graphics_pipeline<'a>(&'a self) -> GraphicsPipelineBuilder<'a> { - GraphicsPipelineBuilder::new(self) - } - - pub fn create_compute_pipeline<'a>(&'a self) -> ComputePipelineBuilder<'a> { - ComputePipelineBuilder::new(self) - } - - #[doc(alias = "SDL_GetGPUShaderFormats")] - pub fn get_shader_formats(&self) -> ShaderFormat { - unsafe { std::mem::transmute(sys::gpu::SDL_GetGPUShaderFormats(self.raw())) } - } - - // NOTE: for Xbox builds, the target is a UWP, e.g.: x86_64-uwp-windows-msvc - #[cfg(target_vendor = "uwp")] - #[doc(alias = "SDL_GDKSuspendGPU")] - pub fn gdk_suspend(&self) { - unsafe { - sys::gpu::SDL_GDKSuspendGPU(self.inner); - } - } - - #[cfg(target_vendor = "uwp")] - #[doc(alias = "SDL_GDKResumeGPU")] - pub fn gdk_resume(&self) { - unsafe { - sys::gpu::SDL_GDKResumeGPU(self.inner); - } - } -} diff --git a/src/sdl3/gpu/enums.rs b/src/sdl3/gpu/enums.rs index 8fc49c1f..8584ee85 100644 --- a/src/sdl3/gpu/enums.rs +++ b/src/sdl3/gpu/enums.rs @@ -1,189 +1,58 @@ use crate::sys; use std::ops::{BitAnd, BitOr}; -use sys::gpu::{ - SDL_GPUBlendFactor, SDL_GPUBlendOp, SDL_GPU_COLORCOMPONENT_A, SDL_GPU_COLORCOMPONENT_B, - SDL_GPU_COLORCOMPONENT_G, SDL_GPU_COLORCOMPONENT_R, -}; +use sys::gpu::{SDL_GPUBlendFactor, SDL_GPUBlendOp}; macro_rules! impl_with { - (bitwise_and_or $x:ident $prim:ident) => { + (bitwise_and_or $x:ident) => { impl BitOr<$x> for $x { type Output = $x; fn bitor(self, rhs: $x) -> Self::Output { - unsafe { std::mem::transmute((self as $prim) | (rhs as $prim)) } + $x(self.0 | rhs.0) } } impl BitAnd<$x> for $x { type Output = $x; fn bitand(self, rhs: $x) -> Self::Output { - unsafe { std::mem::transmute((self as $prim) & (rhs as $prim)) } + $x(self.0 & rhs.0) } } }; } -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum LoadOp { - #[default] - Load = sys::gpu::SDL_GPU_LOADOP_LOAD.0 as u32, - DontCare = sys::gpu::SDL_GPU_LOADOP_DONT_CARE.0 as u32, - Clear = sys::gpu::SDL_GPU_LOADOP_CLEAR.0 as u32, -} -impl_with!(bitwise_and_or LoadOp u32); - -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum StoreOp { - #[default] - Store = sys::gpu::SDL_GPU_STOREOP_STORE.0 as u32, - DontCare = sys::gpu::SDL_GPU_STOREOP_DONT_CARE.0 as u32, - Resolve = sys::gpu::SDL_GPU_STOREOP_RESOLVE.0 as u32, - ResolveAndStore = sys::gpu::SDL_GPU_STOREOP_RESOLVE_AND_STORE.0 as u32, -} -impl_with!(bitwise_and_or StoreOp u32); +pub type LoadOp = sys::gpu::SDL_GPULoadOp; +pub type StoreOp = sys::gpu::SDL_GPUStoreOp; +pub type TextureFormat = sys::gpu::SDL_GPUTextureFormat; #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum TextureFormat { - #[default] - Invalid = sys::gpu::SDL_GPU_TEXTUREFORMAT_INVALID.0 as u32, - A8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_A8_UNORM.0 as u32, - R8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8_UNORM.0 as u32, - R8g8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8_UNORM.0 as u32, - R8g8b8a8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM.0 as u32, - R16Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16_UNORM.0 as u32, - R16g16Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16_UNORM.0 as u32, - R16g16b16a16Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM.0 as u32, - R10g10b10a2Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM.0 as u32, - B5g6r5Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM.0 as u32, - B5g5r5a1Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM.0 as u32, - B4g4r4a4Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM.0 as u32, - B8g8r8a8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM.0 as u32, - Bc1RgbaUnorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM.0 as u32, - Bc2RgbaUnorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM.0 as u32, - Bc3RgbaUnorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM.0 as u32, - Bc4RUnorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM.0 as u32, - Bc5RgUnorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM.0 as u32, - Bc7RgbaUnorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM.0 as u32, - Bc6hRgbFloat = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT.0 as u32, - Bc6hRgbUfloat = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT.0 as u32, - R8Snorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8_SNORM.0 as u32, - R8g8Snorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8_SNORM.0 as u32, - R8g8b8a8Snorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM.0 as u32, - R16Snorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16_SNORM.0 as u32, - R16g16Snorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16_SNORM.0 as u32, - R16g16b16a16Snorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM.0 as u32, - R16Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16_FLOAT.0 as u32, - R16g16Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT.0 as u32, - R16g16b16a16Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT.0 as u32, - R32Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32_FLOAT.0 as u32, - R32g32Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT.0 as u32, - R32g32b32a32Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT.0 as u32, - R11g11b10Ufloat = sys::gpu::SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT.0 as u32, - R8Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8_UINT.0 as u32, - R8g8Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8_UINT.0 as u32, - R8g8b8a8Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT.0 as u32, - R16Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16_UINT.0 as u32, - R16g16Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16_UINT.0 as u32, - R16g16b16a16Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT.0 as u32, - R32Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32_UINT.0 as u32, - R32g32Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32G32_UINT.0 as u32, - R32g32b32a32Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32G32B32A32_UINT.0 as u32, - R8Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8_INT.0 as u32, - R8g8Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8_INT.0 as u32, - R8g8b8a8Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT.0 as u32, - R16Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16_INT.0 as u32, - R16g16Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16_INT.0 as u32, - R16g16b16a16Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT.0 as u32, - R32Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32_INT.0 as u32, - R32g32Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32G32_INT.0 as u32, - R32g32b32a32Int = sys::gpu::SDL_GPU_TEXTUREFORMAT_R32G32B32A32_INT.0 as u32, - R8g8b8a8UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB.0 as u32, - B8g8r8a8UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB.0 as u32, - Bc1RgbaUnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB.0 as u32, - Bc2RgbaUnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB.0 as u32, - Bc3RgbaUnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB.0 as u32, - Bc7RgbaUnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB.0 as u32, - D16Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_D16_UNORM.0 as u32, - D24Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_D24_UNORM.0 as u32, - D32Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_D32_FLOAT.0 as u32, - D24UnormS8Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_D24_UNORM_S8_UINT.0 as u32, - D32FloatS8Uint = sys::gpu::SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT.0 as u32, - Astc4x4Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM.0 as u32, - Astc5x4Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM.0 as u32, - Astc5x5Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM.0 as u32, - Astc6x5Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM.0 as u32, - Astc6x6Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM.0 as u32, - Astc8x5Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM.0 as u32, - Astc8x6Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM.0 as u32, - Astc8x8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM.0 as u32, - Astc10x5Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM.0 as u32, - Astc10x6Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM.0 as u32, - Astc10x8Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM.0 as u32, - Astc10x10Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM.0 as u32, - Astc12x10Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM.0 as u32, - Astc12x12Unorm = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM.0 as u32, - Astc4x4UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB.0 as u32, - Astc5x4UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB.0 as u32, - Astc5x5UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB.0 as u32, - Astc6x5UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB.0 as u32, - Astc6x6UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB.0 as u32, - Astc8x5UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB.0 as u32, - Astc8x6UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB.0 as u32, - Astc8x8UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB.0 as u32, - Astc10x5UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB.0 as u32, - Astc10x6UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB.0 as u32, - Astc10x8UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB.0 as u32, - Astc10x10UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB.0 as u32, - Astc12x10UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB.0 as u32, - Astc12x12UnormSrgb = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB.0 as u32, - Astc4x4Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_4x4_FLOAT.0 as u32, - Astc5x4Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_5x4_FLOAT.0 as u32, - Astc5x5Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_5x5_FLOAT.0 as u32, - Astc6x5Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_6x5_FLOAT.0 as u32, - Astc6x6Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_6x6_FLOAT.0 as u32, - Astc8x5Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x5_FLOAT.0 as u32, - Astc8x6Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x6_FLOAT.0 as u32, - Astc8x8Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_8x8_FLOAT.0 as u32, - Astc10x5Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x5_FLOAT.0 as u32, - Astc10x6Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x6_FLOAT.0 as u32, - Astc10x8Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x8_FLOAT.0 as u32, - Astc10x10Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_10x10_FLOAT.0 as u32, - Astc12x10Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_12x10_FLOAT.0 as u32, - Astc12x12Float = sys::gpu::SDL_GPU_TEXTUREFORMAT_ASTC_12x12_FLOAT.0 as u32, +pub struct ShaderFormat(pub sys::gpu::SDL_GPUShaderFormat); +impl ShaderFormat { + pub const INVALID: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_INVALID); + pub const DXBC: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_DXBC); + pub const DXIL: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_DXIL); + pub const METALLIB: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_METALLIB); + pub const MSL: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_MSL); + pub const PRIVATE: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_PRIVATE); + pub const SPIRV: Self = Self(sys::gpu::SDL_GPU_SHADERFORMAT_SPIRV); } -impl_with!(bitwise_and_or TextureFormat u32); +impl_with!(bitwise_and_or ShaderFormat); -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum ShaderFormat { - #[default] - Invalid = sys::gpu::SDL_GPU_SHADERFORMAT_INVALID as u32, - Dxbc = sys::gpu::SDL_GPU_SHADERFORMAT_DXBC as u32, - Dxil = sys::gpu::SDL_GPU_SHADERFORMAT_DXIL as u32, - MetalLib = sys::gpu::SDL_GPU_SHADERFORMAT_METALLIB as u32, - Msl = sys::gpu::SDL_GPU_SHADERFORMAT_MSL as u32, - Private = sys::gpu::SDL_GPU_SHADERFORMAT_PRIVATE as u32, - SpirV = sys::gpu::SDL_GPU_SHADERFORMAT_SPIRV as u32, +pub struct TextureUsage(pub sys::gpu::SDL_GPUTextureUsageFlags); +impl TextureUsage { + pub const INVALID: Self = Self(0); + pub const COMPUTE_STORAGE_WRITE: Self = + Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE); + pub const COMPUTE_STORAGE_READ: Self = + Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ); + pub const COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE: Self = + Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE); + pub const DEPTH_STENCIL_TARGET: Self = + Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET); + pub const GRAPHICS_STORAGE_READ: Self = + Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ); + pub const SAMPLER: Self = Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_SAMPLER); + pub const COLOR_TARGET: Self = Self(sys::gpu::SDL_GPU_TEXTUREUSAGE_COLOR_TARGET); } -impl_with!(bitwise_and_or ShaderFormat u32); - -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum TextureUsage { - #[default] - Invalid = 0, - ComputeStorageWrite = sys::gpu::SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_WRITE, - ComputeStorageRead = sys::gpu::SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_READ, - ComputeSimultaneousReadWrite = - sys::gpu::SDL_GPU_TEXTUREUSAGE_COMPUTE_STORAGE_SIMULTANEOUS_READ_WRITE, - DepthStencilTarget = sys::gpu::SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET, - GraphicsStorageRead = sys::gpu::SDL_GPU_TEXTUREUSAGE_GRAPHICS_STORAGE_READ, - Sampler = sys::gpu::SDL_GPU_TEXTUREUSAGE_SAMPLER, - ColorTarget = sys::gpu::SDL_GPU_TEXTUREUSAGE_COLOR_TARGET, -} -impl_with!(bitwise_and_or TextureUsage u32); +impl_with!(bitwise_and_or TextureUsage); #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(u32)] @@ -342,14 +211,7 @@ pub enum SamplerAddressMode { ClampToEdge = sys::gpu::SDL_GPUSamplerAddressMode::CLAMP_TO_EDGE.0 as u32, } -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum IndexElementSize { - #[default] - _16Bit = sys::gpu::SDL_GPUIndexElementSize::_16BIT.0 as u32, - _32Bit = sys::gpu::SDL_GPUIndexElementSize::_32BIT.0 as u32, -} -impl_with!(bitwise_and_or IndexElementSize u32); +pub type IndexElementSize = sys::gpu::SDL_GPUIndexElementSize; #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(u32)] @@ -360,25 +222,20 @@ pub enum VertexInputRate { } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum BufferUsageFlags { - #[default] - Vertex = sys::gpu::SDL_GPU_BUFFERUSAGE_VERTEX as u32, - Index = sys::gpu::SDL_GPU_BUFFERUSAGE_INDEX as u32, - Indirect = sys::gpu::SDL_GPU_BUFFERUSAGE_INDIRECT as u32, - GraphicsStorageRead = sys::gpu::SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ as u32, - ComputeStorageRead = sys::gpu::SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ as u32, - ComputeStorageWrite = sys::gpu::SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE as u32, +pub struct BufferUsageFlags(pub sys::gpu::SDL_GPUBufferUsageFlags); +impl BufferUsageFlags { + pub const VERTEX: Self = Self(sys::gpu::SDL_GPU_BUFFERUSAGE_VERTEX); + pub const INDEX: Self = Self(sys::gpu::SDL_GPU_BUFFERUSAGE_INDEX); + pub const INDIRECT: Self = Self(sys::gpu::SDL_GPU_BUFFERUSAGE_INDIRECT); + pub const GRAPHICS_STORAGE_READ: Self = + Self(sys::gpu::SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ); + pub const COMPUTE_STORAGE_READ: Self = Self(sys::gpu::SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_READ); + pub const COMPUTE_STORAGE_WRITE: Self = + Self(sys::gpu::SDL_GPU_BUFFERUSAGE_COMPUTE_STORAGE_WRITE); } +impl_with!(bitwise_and_or BufferUsageFlags); -#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u32)] -pub enum TransferBufferUsage { - #[default] - Upload = sys::gpu::SDL_GPUTransferBufferUsage::UPLOAD.0 as u32, - Download = sys::gpu::SDL_GPUTransferBufferUsage::DOWNLOAD.0 as u32, -} -impl_with!(bitwise_and_or TransferBufferUsage u32); +pub type TransferBufferUsage = sys::gpu::SDL_GPUTransferBufferUsage; #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[repr(u32)] @@ -413,12 +270,12 @@ pub enum BlendOp { } #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[repr(u8)] -pub enum ColorComponentFlags { - #[default] - RBit = SDL_GPU_COLORCOMPONENT_R, - GBit = SDL_GPU_COLORCOMPONENT_G, - BBit = SDL_GPU_COLORCOMPONENT_B, - ABit = SDL_GPU_COLORCOMPONENT_A, +pub struct ColorComponentFlags(pub sys::gpu::SDL_GPUColorComponentFlags); + +impl ColorComponentFlags { + pub const R: Self = Self(sys::gpu::SDL_GPU_COLORCOMPONENT_R); + pub const G: Self = Self(sys::gpu::SDL_GPU_COLORCOMPONENT_G); + pub const B: Self = Self(sys::gpu::SDL_GPU_COLORCOMPONENT_B); + pub const A: Self = Self(sys::gpu::SDL_GPU_COLORCOMPONENT_A); } -impl_with!(bitwise_and_or ColorComponentFlags u8); +impl_with!(bitwise_and_or ColorComponentFlags); diff --git a/src/sdl3/gpu/info_struct.rs b/src/sdl3/gpu/info_struct.rs new file mode 100644 index 00000000..db71986d --- /dev/null +++ b/src/sdl3/gpu/info_struct.rs @@ -0,0 +1,858 @@ +//! Types which hold data but don't own any GPU-resources. +//! There are no calls to SDL here. +//! + +use std::marker::PhantomData; + +use sys::gpu::{ + SDL_GPUBlendFactor, SDL_GPUBlendOp, SDL_GPUBufferBinding, SDL_GPUBufferLocation, + SDL_GPUBufferRegion, SDL_GPUColorTargetBlendState, SDL_GPUColorTargetDescription, + SDL_GPUColorTargetInfo, SDL_GPUCompareOp, SDL_GPUCullMode, SDL_GPUDepthStencilState, + SDL_GPUDepthStencilTargetInfo, SDL_GPUFillMode, SDL_GPUFilter, SDL_GPUFrontFace, + SDL_GPUGraphicsPipelineTargetInfo, SDL_GPUIndirectDispatchCommand, SDL_GPURasterizerState, + SDL_GPUSampleCount, SDL_GPUSamplerAddressMode, SDL_GPUSamplerCreateInfo, + SDL_GPUSamplerMipmapMode, SDL_GPUStencilOp, SDL_GPUStencilOpState, + SDL_GPUStorageBufferReadWriteBinding, SDL_GPUStorageTextureReadWriteBinding, + SDL_GPUTextureCreateInfo, SDL_GPUTextureLocation, SDL_GPUTextureRegion, + SDL_GPUTextureSamplerBinding, SDL_GPUTextureTransferInfo, SDL_GPUTextureType, + SDL_GPUTransferBufferLocation, SDL_GPUVertexAttribute, SDL_GPUVertexBufferDescription, + SDL_GPUVertexInputRate, SDL_GPUVertexInputState, +}; + +use crate::pixels::Color; + +use super::{ + BlendFactor, BlendOp, Buffer, ColorComponentFlags, CompareOp, CullMode, FillMode, Filter, + FrontFace, LoadOp, SampleCount, Sampler, SamplerAddressMode, SamplerMipmapMode, StencilOp, + StoreOp, Texture, TextureFormat, TextureType, TextureUsage, TransferBuffer, + VertexElementFormat, VertexInputRate, +}; + +#[repr(transparent)] +#[derive(Default)] +pub struct DepthStencilTargetInfo<'a> { + inner: SDL_GPUDepthStencilTargetInfo, + _marker: PhantomData<&'a Texture>, +} +impl<'a> DepthStencilTargetInfo<'a> { + pub fn new() -> Self { + Default::default() + } + + pub fn with_texture(mut self, texture: &'a Texture) -> Self { + self.inner.texture = texture.ll(); + self + } + + pub fn with_clear_depth(mut self, clear_depth: f32) -> Self { + self.inner.clear_depth = clear_depth; + self + } + + pub fn with_load_op(mut self, value: LoadOp) -> Self { + self.inner.load_op = value; + self + } + + pub fn with_store_op(mut self, value: StoreOp) -> Self { + self.inner.store_op = value; + self + } + + pub fn with_stencil_load_op(mut self, value: LoadOp) -> Self { + self.inner.stencil_load_op = value; + self + } + + pub fn with_stencil_store_op(mut self, value: StoreOp) -> Self { + self.inner.stencil_store_op = value; + self + } + + pub fn with_cycle(mut self, cycle: bool) -> Self { + self.inner.cycle = cycle; + self + } + + pub fn with_clear_stencil(mut self, clear_stencil: u8) -> Self { + self.inner.clear_stencil = clear_stencil; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct ColorTargetInfo<'a> { + inner: SDL_GPUColorTargetInfo, + _marker: PhantomData<&'a Texture>, +} +impl<'a> ColorTargetInfo<'a> { + pub fn new() -> Self { + Default::default() + } + + pub fn with_texture(mut self, texture: &'a Texture) -> Self { + self.inner.texture = texture.ll(); + self + } + + pub fn with_load_op(mut self, value: LoadOp) -> Self { + self.inner.load_op = value; + self + } + + pub fn with_store_op(mut self, value: StoreOp) -> Self { + self.inner.store_op = value; + self + } + + pub fn with_clear_color(mut self, value: Color) -> Self { + self.inner.clear_color.r = (value.r as f32) / 255.0; + self.inner.clear_color.g = (value.g as f32) / 255.0; + self.inner.clear_color.b = (value.b as f32) / 255.0; + self.inner.clear_color.a = (value.a as f32) / 255.0; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct TextureCreateInfo { + pub(super) inner: SDL_GPUTextureCreateInfo, +} +impl TextureCreateInfo { + pub fn new() -> Self { + Default::default() + } + + /// The base dimensionality of the texture. + pub fn with_type(mut self, value: TextureType) -> Self { + self.inner.r#type = SDL_GPUTextureType(value as i32); + self + } + + /// The pixel format of the texture. + pub fn with_format(mut self, format: TextureFormat) -> Self { + self.inner.format = format; + self + } + + /// How the texture is intended to be used by the client. + pub fn with_usage(mut self, value: TextureUsage) -> Self { + self.inner.usage = value.0; + self + } + + /// The width of the texture. + pub fn with_width(mut self, value: u32) -> Self { + self.inner.width = value; + self + } + + /// The height of the texture. + pub fn with_height(mut self, value: u32) -> Self { + self.inner.height = value; + self + } + + /// The layer count or depth of the texture. This value is treated as a layer count on 2D array textures, and as a depth value on 3D textures. + pub fn with_layer_count_or_depth(mut self, value: u32) -> Self { + self.inner.layer_count_or_depth = value; + self + } + + /// The number of mip levels in the texture. + pub fn with_num_levels(mut self, value: u32) -> Self { + self.inner.num_levels = value; + self + } + + /// The number of samples per texel. Only applies if the texture is used as a render target. + pub fn with_sample_count(mut self, value: SampleCount) -> Self { + self.inner.sample_count = SDL_GPUSampleCount(value as i32); + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct SamplerCreateInfo { + pub(super) inner: SDL_GPUSamplerCreateInfo, +} +impl SamplerCreateInfo { + pub fn new() -> Self { + Default::default() + } + + /// The minification filter to apply to lookups. + pub fn with_min_filter(mut self, filter: Filter) -> Self { + self.inner.min_filter = SDL_GPUFilter(filter as i32); + self + } + + /// The magnification filter to apply to lookups. + pub fn with_mag_filter(mut self, filter: Filter) -> Self { + self.inner.mag_filter = SDL_GPUFilter(filter as i32); + self + } + + /// The mipmap filter to apply to lookups. + pub fn with_mipmap_mode(mut self, mode: SamplerMipmapMode) -> Self { + self.inner.mipmap_mode = SDL_GPUSamplerMipmapMode(mode as i32); + self + } + + /// The addressing mode for U coordinates outside [0, 1). + pub fn with_address_mode_u(mut self, mode: SamplerAddressMode) -> Self { + self.inner.address_mode_u = SDL_GPUSamplerAddressMode(mode as i32); + self + } + + /// The addressing mode for V coordinates outside [0, 1). + pub fn with_address_mode_v(mut self, mode: SamplerAddressMode) -> Self { + self.inner.address_mode_v = SDL_GPUSamplerAddressMode(mode as i32); + self + } + + /// The addressing mode for W coordinates outside [0, 1). + pub fn with_address_mode_w(mut self, mode: SamplerAddressMode) -> Self { + self.inner.address_mode_w = SDL_GPUSamplerAddressMode(mode as i32); + self + } + + /// The bias to be added to mipmap LOD calculation. + pub fn with_mip_lod_bias(mut self, value: f32) -> Self { + self.inner.mip_lod_bias = value; + self + } + + /// The anisotropy value clamp used by the sampler. If enable_anisotropy is false, this is ignored. + pub fn with_max_anisotropy(mut self, value: f32) -> Self { + self.inner.max_anisotropy = value; + self + } + + /// The comparison operator to apply to fetched data before filtering. + pub fn with_compare_op(mut self, value: CompareOp) -> Self { + self.inner.compare_op = SDL_GPUCompareOp(value as i32); + self + } + + /// Clamps the minimum of the computed LOD value. + pub fn with_min_lod(mut self, value: f32) -> Self { + self.inner.min_lod = value; + self + } + + /// Clamps the maximum of the computed LOD value. + pub fn with_max_lod(mut self, value: f32) -> Self { + self.inner.max_lod = value; + self + } + + /// True to enable anisotropic filtering. + pub fn with_enable_anisotropy(mut self, enable: bool) -> Self { + self.inner.enable_anisotropy = enable; + self + } + + /// True to enable comparison against a reference value during lookups. + pub fn with_enable_compare(mut self, enable: bool) -> Self { + self.inner.enable_compare = enable; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct TextureRegion<'a> { + pub(super) inner: SDL_GPUTextureRegion, + _marker: PhantomData<&'a Texture>, +} +impl<'a> TextureRegion<'a> { + pub fn new() -> Self { + Default::default() + } + + /// The texture used in the copy operation. + pub fn with_texture(mut self, texture: &'a Texture) -> Self { + self.inner.texture = texture.ll(); + self + } + + /// The mip level index to transfer. + pub fn with_mip_level(mut self, mip_level: u32) -> Self { + self.inner.mip_level = mip_level; + self + } + + /// The layer index to transfer. + pub fn with_layer(mut self, layer: u32) -> Self { + self.inner.layer = layer; + self + } + + /// The left offset of the region. + pub fn with_x(mut self, x: u32) -> Self { + self.inner.x = x; + self + } + + /// The top offset of the region. + pub fn with_y(mut self, y: u32) -> Self { + self.inner.y = y; + self + } + + /// The front offset of the region. + pub fn with_z(mut self, z: u32) -> Self { + self.inner.z = z; + self + } + + /// The width of the region. + pub fn with_width(mut self, width: u32) -> Self { + self.inner.w = width; + self + } + + /// The height of the region. + pub fn with_height(mut self, height: u32) -> Self { + self.inner.h = height; + self + } + + /// The depth of the region. + pub fn with_depth(mut self, depth: u32) -> Self { + self.inner.d = depth; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct TextureTransferInfo<'a> { + pub(super) inner: SDL_GPUTextureTransferInfo, + _marker: PhantomData<&'a TransferBuffer>, +} +impl<'a> TextureTransferInfo<'a> { + pub fn new() -> Self { + Default::default() + } + + /// The transfer buffer used in the transfer operation. + pub fn with_transfer_buffer(mut self, buffer: &'a TransferBuffer) -> Self { + self.inner.transfer_buffer = buffer.ll(); + self + } + + /// The starting byte of the image data in the transfer buffer. + pub fn with_offset(mut self, offset: u32) -> Self { + self.inner.offset = offset; + self + } + + /// The number of pixels from one row to the next. + pub fn with_pixels_per_row(mut self, value: u32) -> Self { + self.inner.pixels_per_row = value; + self + } + + /// The number of rows from one layer/depth-slice to the next. + pub fn with_rows_per_layer(mut self, value: u32) -> Self { + self.inner.rows_per_layer = value; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct BufferBinding<'a> { + pub(super) inner: SDL_GPUBufferBinding, + _marker: PhantomData<&'a Buffer>, +} +impl<'a> BufferBinding<'a> { + pub fn new() -> Self { + Default::default() + } + + pub fn with_buffer(mut self, buffer: &'a Buffer) -> Self { + self.inner.buffer = buffer.ll(); + self + } + + pub fn with_offset(mut self, offset: u32) -> Self { + self.inner.offset = offset; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct TransferBufferLocation<'a> { + pub(super) inner: SDL_GPUTransferBufferLocation, + pub(super) _marker: PhantomData<&'a TransferBuffer>, +} + +#[repr(C)] +#[derive(Default)] +pub struct BufferLocation<'a> { + pub(super) inner: SDL_GPUBufferLocation, + pub(super) _marker: PhantomData<&'a Buffer>, +} + +#[repr(C)] +#[derive(Default)] +pub struct BufferRegion<'a> { + pub(super) inner: SDL_GPUBufferRegion, + pub(super) _marker: PhantomData<&'a Buffer>, +} + +#[repr(C)] +#[derive(Clone, Default)] +pub struct VertexBufferDescription { + inner: SDL_GPUVertexBufferDescription, +} +impl VertexBufferDescription { + pub fn new() -> Self { + Default::default() + } + + pub fn with_slot(mut self, value: u32) -> Self { + self.inner.slot = value; + self + } + + pub fn with_pitch(mut self, value: u32) -> Self { + self.inner.pitch = value; + self + } + + pub fn with_input_rate(mut self, value: VertexInputRate) -> Self { + self.inner.input_rate = SDL_GPUVertexInputRate(value as i32); + self + } + + pub fn with_instance_step_rate(mut self, value: u32) -> Self { + self.inner.instance_step_rate = value; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct VertexInputState<'a> { + pub(super) inner: SDL_GPUVertexInputState, + _marker: PhantomData<(&'a [VertexBufferDescription], &'a [VertexAttribute])>, +} +impl<'a> VertexInputState<'a> { + pub fn new() -> Self { + Default::default() + } + + pub fn with_vertex_buffer_descriptions(mut self, value: &'a [VertexBufferDescription]) -> Self { + self.inner.vertex_buffer_descriptions = + value.as_ptr() as *const SDL_GPUVertexBufferDescription; + self.inner.num_vertex_buffers = value.len() as u32; + self + } + + pub fn with_vertex_attributes(mut self, value: &'a [VertexAttribute]) -> Self { + self.inner.vertex_attributes = value.as_ptr() as *const SDL_GPUVertexAttribute; + self.inner.num_vertex_attributes = value.len() as u32; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct RasterizerState { + pub(super) inner: SDL_GPURasterizerState, +} +impl RasterizerState { + pub fn new() -> Self { + Default::default() + } + + /// Whether polygons will be filled in or drawn as lines. + pub fn with_fill_mode(mut self, fill_mode: FillMode) -> Self { + self.inner.fill_mode = SDL_GPUFillMode(fill_mode as i32); + self + } + + /// The facing direction in which triangles will be culled. + pub fn with_cull_mode(mut self, cull_mode: CullMode) -> Self { + self.inner.cull_mode = SDL_GPUCullMode(cull_mode as i32); + self + } + + /// The vertex winding that will cause a triangle to be determined as front-facing. + pub fn with_front_face(mut self, front_face: FrontFace) -> Self { + self.inner.front_face = SDL_GPUFrontFace(front_face as i32); + self + } + + /// A scalar factor controlling the depth value added to each fragment. + pub fn with_depth_bias_constant_factor(mut self, value: f32) -> Self { + self.inner.depth_bias_constant_factor = value; + self + } + + /// The maximum depth bias of a fragment. + pub fn with_depth_bias_clamp(mut self, value: f32) -> Self { + self.inner.depth_bias_clamp = value; + self + } + + /// A scalar factor applied to a fragment's slope in depth calculations. + pub fn with_depth_slope_factor(mut self, value: f32) -> Self { + self.inner.depth_bias_slope_factor = value; + self + } + + /// True to bias fragment depth values. + pub fn with_enable_depth_bias(mut self, value: bool) -> Self { + self.inner.enable_depth_bias = value; + self + } + + /// True to enable depth clip, false to enable depth clamp. + pub fn with_enable_depth_clip(mut self, value: bool) -> Self { + self.inner.enable_depth_clip = value; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct StencilOpState { + pub(super) inner: SDL_GPUStencilOpState, +} +impl StencilOpState { + pub fn new() -> Self { + Default::default() + } + + /// The comparison operator used in the stencil test. + pub fn with_compare_op(mut self, value: CompareOp) -> Self { + self.inner.compare_op = SDL_GPUCompareOp(value as i32); + self + } + + /// The action performed on samples that fail the stencil test. + pub fn with_fail_op(mut self, value: StencilOp) -> Self { + self.inner.fail_op = SDL_GPUStencilOp(value as i32); + self + } + + /// The action performed on samples that pass the depth and stencil tests. + pub fn with_pass_op(mut self, value: StencilOp) -> Self { + self.inner.pass_op = SDL_GPUStencilOp(value as i32); + self + } + + /// The action performed on samples that pass the stencil test and fail the depth test. + pub fn with_depth_fail_op(mut self, value: StencilOp) -> Self { + self.inner.depth_fail_op = SDL_GPUStencilOp(value as i32); + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct DepthStencilState { + pub(super) inner: SDL_GPUDepthStencilState, +} +impl DepthStencilState { + pub fn new() -> Self { + Default::default() + } + + /// The comparison operator used for depth testing. + pub fn with_compare_op(mut self, value: CompareOp) -> Self { + self.inner.compare_op = SDL_GPUCompareOp(value as i32); + self + } + + /// The stencil op state for back-facing triangles. + pub fn with_back_stencil_state(mut self, value: StencilOpState) -> Self { + self.inner.back_stencil_state = value.inner; + self + } + + /// The stencil op state for front-facing triangles. + pub fn with_front_stencil_state(mut self, value: StencilOpState) -> Self { + self.inner.front_stencil_state = value.inner; + self + } + + /// Selects the bits of the stencil values participating in the stencil test. + pub fn with_compare_mask(mut self, value: u8) -> Self { + self.inner.compare_mask = value; + self + } + + /// Selects the bits of the stencil values updated by the stencil test. + pub fn with_write_mask(mut self, value: u8) -> Self { + self.inner.write_mask = value; + self + } + + /// True enables the depth test. + pub fn with_enable_depth_test(mut self, value: bool) -> Self { + self.inner.enable_depth_test = value; + self + } + + /// True enables depth writes. + pub fn with_enable_depth_write(mut self, value: bool) -> Self { + self.inner.enable_depth_write = value; + self + } + + /// True enables the stencil test. + pub fn with_enable_stencil_test(mut self, value: bool) -> Self { + self.inner.enable_stencil_test = value; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct GraphicsPipelineTargetInfo<'a> { + pub(super) inner: SDL_GPUGraphicsPipelineTargetInfo, + _marker: PhantomData<&'a [ColorTargetDescription]>, +} +impl<'a> GraphicsPipelineTargetInfo<'a> { + pub fn new() -> Self { + Default::default() + } + + /// A pointer to an array of color target descriptions. + pub fn with_color_target_descriptions(mut self, value: &'a [ColorTargetDescription]) -> Self { + self.inner.color_target_descriptions = + value.as_ptr() as *const SDL_GPUColorTargetDescription; + self.inner.num_color_targets = value.len() as u32; + self + } + + /// The pixel format of the depth-stencil target. Ignored if has_depth_stencil_target is false. + pub fn with_depth_stencil_format(mut self, value: TextureFormat) -> Self { + self.inner.depth_stencil_format = value; + self + } + + /// true specifies that the pipeline uses a depth-stencil target. + pub fn with_has_depth_stencil_target(mut self, value: bool) -> Self { + self.inner.has_depth_stencil_target = value; + self + } +} + +#[repr(C)] +#[derive(Clone, Default)] +pub struct VertexAttribute { + inner: SDL_GPUVertexAttribute, +} +impl VertexAttribute { + pub fn new() -> Self { + Default::default() + } + + /// The shader input location index. + pub fn with_location(mut self, value: u32) -> Self { + self.inner.location = value; + self + } + + /// The binding slot of the associated vertex buffer. + pub fn with_buffer_slot(mut self, value: u32) -> Self { + self.inner.buffer_slot = value; + self + } + + /// The size and type of the attribute data. + pub fn with_format(mut self, value: VertexElementFormat) -> Self { + self.inner.format = unsafe { std::mem::transmute(value as u32) }; + self + } + + /// The byte offset of this attribute relative to the start of the vertex element. + pub fn with_offset(mut self, value: u32) -> Self { + self.inner.offset = value; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct ColorTargetBlendState { + inner: SDL_GPUColorTargetBlendState, +} +impl ColorTargetBlendState { + pub fn new() -> Self { + Self::default() + } + + /// The value to be multiplied by the source RGB value. + pub fn with_src_color_blendfactor(mut self, blend_factor: BlendFactor) -> Self { + self.inner.src_color_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); + self + } + + /// The value to be multiplied by the destination RGB value. + pub fn with_dst_color_blendfactor(mut self, blend_factor: BlendFactor) -> Self { + self.inner.dst_color_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); + self + } + + /// The blend operation for the RGB components. + pub fn with_color_blend_op(mut self, blend_op: BlendOp) -> Self { + self.inner.color_blend_op = SDL_GPUBlendOp(blend_op as i32); + self + } + + /// The value to be multiplied by the source alpha. + pub fn with_src_alpha_blendfactor(mut self, blend_factor: BlendFactor) -> Self { + self.inner.src_alpha_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); + self + } + + /// The value to be multiplied by the destination alpha. + pub fn with_dst_alpha_blendfactor(mut self, blend_factor: BlendFactor) -> Self { + self.inner.dst_alpha_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); + self + } + + /// The blend operation for the alpha component. + pub fn with_alpha_blend_op(mut self, blend_op: BlendOp) -> Self { + self.inner.alpha_blend_op = SDL_GPUBlendOp(blend_op as i32); + self + } + + /// A bitmask specifying which of the RGBA components are enabled for writing. Writes to all channels if enable_color_write_mask is false. + pub fn with_color_write_mask(mut self, flags: ColorComponentFlags) -> Self { + self.inner.color_write_mask = flags.0; + self + } + + /// Whether blending is enabled for the color target. + pub fn with_enable_blend(mut self, enable: bool) -> Self { + self.inner.enable_blend = enable; + self + } + + /// Whether the color write mask is enabled. + pub fn with_enable_color_write_mask(mut self, enable: bool) -> Self { + self.inner.enable_color_write_mask = enable; + self + } +} + +#[repr(C)] +#[derive(Default, Copy, Clone)] +pub struct ColorTargetDescription { + inner: SDL_GPUColorTargetDescription, +} +impl ColorTargetDescription { + pub fn new() -> Self { + Self::default() + } + + /// The pixel format of the texture to be used as a color target. + pub fn with_format(mut self, value: TextureFormat) -> Self { + self.inner.format = value; + self + } + + /// The blend state to be used for the color target. + pub fn with_blend_state(mut self, value: ColorTargetBlendState) -> Self { + self.inner.blend_state = value.inner; + self + } +} + +#[repr(C)] +#[derive(Default)] +pub struct TextureSamplerBinding<'a> { + pub(crate) inner: SDL_GPUTextureSamplerBinding, + _marker: PhantomData<(&'a Texture, &'a Sampler)>, +} +impl<'a> TextureSamplerBinding<'a> { + // pub fn new() -> Self { + // Default::default() + // } + + // /// The texture to bind. Must have been created with [`SDL_GPU_TEXTUREUSAGE_SAMPLER`]. + // pub fn with_texture(mut self, texture: &'a Texture) -> Self { + // self.inner.texture = texture.ll(); + // self + // } + + // /// The sampler to bind. + // pub fn with_sampler(mut self, sampler: &'a Sampler) -> Self { + // self.inner.sampler = sampler.ll(); + // self + // } +} + +#[repr(C)] +#[derive(Default)] +pub struct StorageTextureReadWriteBinding<'a> { + pub(crate) inner: SDL_GPUStorageTextureReadWriteBinding, + pub(crate) _marker: PhantomData<&'a Texture>, +} +impl<'a> StorageTextureReadWriteBinding<'a> { + // pub fn new() -> Self { + // Default::default() + // } + + // pub fn with_texture(mut self, texture: &'a Texture) -> Self { + // self.inner.texture = texture.ll(); + // self + // } + + pub fn with_mip_level(mut self, mip_level: u32) -> Self { + self.inner.mip_level = mip_level; + self + } + + pub fn with_layer(mut self, layer: u32) -> Self { + self.inner.layer = layer; + self + } + + // pub fn with_cycle(mut self, cycle: bool) -> Self { + // self.inner.cycle = cycle; + // self + // } +} + +#[repr(C)] +#[derive(Default)] +pub struct StorageBufferReadWriteBinding<'a> { + pub(crate) inner: SDL_GPUStorageBufferReadWriteBinding, + pub(crate) _marker: PhantomData<&'a Buffer>, +} +impl<'a> StorageBufferReadWriteBinding<'a> { + // pub fn new() -> Self { + // Default::default() + // } + + // pub fn with_buffer(mut self, buffer: &'a Buffer) -> Self { + // self.inner.buffer = buffer.ll(); + // self + // } + + // pub fn with_cycle(mut self, cycle: bool) -> Self { + // self.inner.cycle = cycle; + // self + // } +} + +pub type IndirectDispatchCommand = SDL_GPUIndirectDispatchCommand; + +pub struct TextureLocation<'a> { + pub(crate) inner: SDL_GPUTextureLocation, + pub(crate) _marker: PhantomData<&'a Texture>, +} diff --git a/src/sdl3/gpu/mod.rs b/src/sdl3/gpu/mod.rs index cf7b36b7..195166f6 100644 --- a/src/sdl3/gpu/mod.rs +++ b/src/sdl3/gpu/mod.rs @@ -1,13 +1,24 @@ -use crate::gpu::device::WeakDevice; +use std::cell::UnsafeCell; +use std::marker::{PhantomData, PhantomPinned}; -mod buffer; -pub use buffer::{ - Buffer, BufferBinding, BufferBuilder, BufferMemMap, BufferRegion, TransferBuffer, - TransferBufferBuilder, TransferBufferLocation, VertexBufferDescription, +mod abstraction; + +mod auto_trait; + +pub use abstraction::Ref; + +mod resource; +pub use resource::{ + Buffer, ComputePipeline, Device, GraphicsPipeline, Owned, OwnedDevice, Sampler, Shader, + Texture, TransferBuffer, +}; +pub use resource::{ + BufferBuilder, ComputePipelineBuilder, GraphicsPipelineBuilder, ShaderBuilder, + TransferBufferBuilder, }; -mod device; -pub use device::Device; +mod command_buffer; +pub use command_buffer::{CommandBuffer, ComputePass, CopyPass, Fence, RenderPass}; mod enums; pub use enums::{ @@ -17,23 +28,81 @@ pub use enums::{ TextureUsage, TransferBufferUsage, VertexElementFormat, VertexInputRate, }; -mod pass; -pub use pass::{ - ColorTargetInfo, CommandBuffer, ComputePass, CopyPass, DepthStencilTargetInfo, RenderPass, +mod info_struct; +pub use info_struct::{ + BufferBinding, BufferRegion, ColorTargetBlendState, ColorTargetDescription, ColorTargetInfo, + DepthStencilState, DepthStencilTargetInfo, GraphicsPipelineTargetInfo, RasterizerState, + SamplerCreateInfo, StencilOpState, StorageBufferReadWriteBinding, + StorageTextureReadWriteBinding, TextureCreateInfo, TextureRegion, TextureSamplerBinding, + TextureTransferInfo, TransferBufferLocation, VertexAttribute, VertexBufferDescription, + VertexInputState, }; -mod pipeline; -pub use pipeline::{ - ColorTargetBlendState, ColorTargetDescription, ComputePipeline, ComputePipelineBuilder, - DepthStencilState, GraphicsPipeline, GraphicsPipelineBuilder, GraphicsPipelineTargetInfo, - RasterizerState, StencilOpState, VertexAttribute, VertexInputState, +use sys::gpu::{ + SDL_ClaimWindowForGPUDevice, SDL_GetGPUSwapchainTextureFormat, SDL_ReleaseWindowFromGPUDevice, }; -mod texture; -pub use texture::{ - Sampler, SamplerCreateInfo, Texture, TextureCreateInfo, TextureRegion, TextureSamplerBinding, - TextureTransferInfo, -}; +use crate::{get_error, Error}; + +mod util; + +unsafe impl Sync for Device {} +unsafe impl Sync for Buffer {} +unsafe impl Sync for Texture {} +unsafe impl<'a, T: resource::GpuRelease + Sync> Sync for Owned<'a, T> {} + +// We need some wrapper to be able to implement (inherent) methods for the type. +// The UnsafeCell doesn't actually do anything for &mut Extern, but the wrapped types +// are also zero-sized, so safe code still can't access any bytes with that. +// Also, PhantomPinned so that we can safely give out Pin<&mut Extern> +#[repr(transparent)] +pub struct Extern(UnsafeCell, PhantomPinned, PhantomData<*mut ()>); + +impl Extern { + pub fn ll(&self) -> *mut T { + self.0.get() + } +} + +impl Device { + #[doc(alias = "SDL_ClaimWindowForGPUDevice")] + pub fn claim_window(&self, w: &crate::video::Window) -> Result<(), Error> { + let p = unsafe { SDL_ClaimWindowForGPUDevice(self.ll(), w.raw()) }; + if p { + Ok(()) + } else { + Err(get_error()) + } + } + + #[doc(alias = "SDL_ClaimWindowForGPUDevice")] + pub fn release_window(&self, w: &crate::video::Window) { + unsafe { SDL_ReleaseWindowFromGPUDevice(self.ll(), w.raw()) }; + } + + #[doc(alias = "SDL_GetGPUSwapchainTextureFormat")] + pub fn get_swapchain_texture_format(&self, w: &crate::video::Window) -> TextureFormat { + unsafe { SDL_GetGPUSwapchainTextureFormat(self.ll(), w.raw()) } + } + + #[doc(alias = "SDL_GetGPUShaderFormats")] + pub fn get_shader_formats(&self) -> ShaderFormat { + unsafe { ShaderFormat(sys::gpu::SDL_GetGPUShaderFormats(self.ll())) } + } + + #[cfg(target_os = "xbox")] + #[doc(alias = "SDL_GDKSuspendGPU")] + pub fn gdk_suspend(&self) { + unsafe { + sys::gpu::SDL_GDKSuspendGPU(self.raw()); + } + } -mod shader; -pub use shader::{Shader, ShaderBuilder}; + #[cfg(target_os = "xbox")] + #[doc(alias = "SDL_GDKResumeGPU")] + pub fn gdk_resume(&self) { + unsafe { + sys::gpu::SDL_GDKResumeGPU(self.raw()); + } + } +} diff --git a/src/sdl3/gpu/pass.rs b/src/sdl3/gpu/pass.rs deleted file mode 100644 index f36ba158..00000000 --- a/src/sdl3/gpu/pass.rs +++ /dev/null @@ -1,443 +0,0 @@ -use crate::{ - get_error, - gpu::{ - BufferBinding, BufferRegion, GraphicsPipeline, IndexElementSize, LoadOp, StoreOp, Texture, - TextureRegion, TextureSamplerBinding, TextureTransferInfo, TransferBufferLocation, - }, - pixels::Color, - Error, -}; -use sys::gpu::{ - SDL_AcquireGPUSwapchainTexture, SDL_BindGPUFragmentSamplers, SDL_BindGPUIndexBuffer, - SDL_BindGPUVertexBuffers, SDL_DrawGPUIndexedPrimitives, SDL_GPUBufferBinding, - SDL_GPUColorTargetInfo, SDL_GPUCommandBuffer, SDL_GPUComputePass, SDL_GPUCopyPass, - SDL_GPUDepthStencilTargetInfo, SDL_GPUIndexElementSize, SDL_GPULoadOp, SDL_GPURenderPass, - SDL_GPUStoreOp, SDL_GPUTextureSamplerBinding, SDL_PushGPUComputeUniformData, - SDL_PushGPUFragmentUniformData, SDL_PushGPUVertexUniformData, SDL_UploadToGPUBuffer, - SDL_UploadToGPUTexture, SDL_WaitAndAcquireGPUSwapchainTexture, -}; - -use super::{Buffer, ComputePipeline}; - -pub struct CommandBuffer { - pub(super) inner: *mut SDL_GPUCommandBuffer, -} -impl CommandBuffer { - pub(super) fn new(inner: *mut SDL_GPUCommandBuffer) -> Self { - Self { inner } - } - - #[inline] - pub fn raw(&self) -> *mut SDL_GPUCommandBuffer { - self.inner - } - - #[doc(alias = "SDL_PushGPUVertexUniformData")] - pub fn push_vertex_uniform_data(&self, slot_index: u32, data: &T) { - unsafe { - SDL_PushGPUVertexUniformData( - self.raw(), - slot_index, - (data as *const T) as *const std::ffi::c_void, - size_of::() as u32, - ) - } - } - - #[doc(alias = "SDL_PushGPUFragmentUniformData")] - pub fn push_fragment_uniform_data(&self, slot_index: u32, data: &T) { - unsafe { - SDL_PushGPUFragmentUniformData( - self.raw(), - slot_index, - (data as *const T) as *const std::ffi::c_void, - size_of::() as u32, - ) - } - } - - #[doc(alias = "SDL_PushGPUComputeUniformData")] - pub fn push_compute_uniform_data(&self, slot_index: u32, data: &T) { - unsafe { - SDL_PushGPUComputeUniformData( - self.raw(), - slot_index, - (data as *const T) as *const std::ffi::c_void, - size_of::() as u32, - ) - } - } - - #[doc(alias = "SDL_WaitAndAcquireGPUSwapchainTexture")] - pub fn wait_and_acquire_swapchain_texture<'a>( - &'a mut self, - w: &crate::video::Window, - ) -> Result, Error> { - let mut swapchain = std::ptr::null_mut(); - let mut width = 0; - let mut height = 0; - let success = unsafe { - SDL_WaitAndAcquireGPUSwapchainTexture( - self.inner, - w.raw(), - &mut swapchain, - &mut width, - &mut height, - ) - }; - if success { - Ok(Texture::new_sdl_managed(swapchain, width, height)) - } else { - Err(get_error()) - } - } - - #[doc(alias = "SDL_AcquireGPUSwapchainTexture")] - pub fn acquire_swapchain_texture<'a>( - &'a mut self, - w: &crate::video::Window, - ) -> Result, Error> { - let mut swapchain = std::ptr::null_mut(); - let mut width = 0; - let mut height = 0; - let success = unsafe { - SDL_AcquireGPUSwapchainTexture( - self.inner, - w.raw(), - &mut swapchain, - &mut width, - &mut height, - ) - }; - if success { - Ok(Texture::new_sdl_managed(swapchain, width, height)) - } else { - Err(get_error()) - } - } - - #[doc(alias = "SDL_SubmitGPUCommandBuffer")] - pub fn submit(self) -> Result<(), Error> { - if unsafe { sys::gpu::SDL_SubmitGPUCommandBuffer(self.inner) } { - Ok(()) - } else { - Err(get_error()) - } - } - - #[doc(alias = "SDL_CancelGPUCommandBuffer")] - pub fn cancel(&mut self) { - unsafe { - sys::gpu::SDL_CancelGPUCommandBuffer(self.inner); - } - } -} - -#[repr(C)] -#[derive(Default)] -pub struct DepthStencilTargetInfo { - inner: SDL_GPUDepthStencilTargetInfo, -} -impl DepthStencilTargetInfo { - pub fn new() -> Self { - Default::default() - } - - pub fn with_texture(mut self, texture: &mut Texture) -> Self { - self.inner.texture = texture.raw(); - self - } - - pub fn with_clear_depth(mut self, clear_depth: f32) -> Self { - self.inner.clear_depth = clear_depth; - self - } - - pub fn with_load_op(mut self, value: LoadOp) -> Self { - self.inner.load_op = SDL_GPULoadOp(value as i32); - self - } - - pub fn with_store_op(mut self, value: StoreOp) -> Self { - self.inner.store_op = SDL_GPUStoreOp(value as i32); - self - } - - pub fn with_stencil_load_op(mut self, value: LoadOp) -> Self { - self.inner.stencil_load_op = - unsafe { std::mem::transmute::<_, sys::gpu::SDL_GPULoadOp>(value as u32) }; - self - } - - pub fn with_stencil_store_op(mut self, value: StoreOp) -> Self { - self.inner.stencil_store_op = - unsafe { std::mem::transmute::<_, sys::gpu::SDL_GPUStoreOp>(value as u32) }; - self - } - - pub fn with_cycle(mut self, cycle: bool) -> Self { - self.inner.cycle = cycle; - self - } - - pub fn with_clear_stencil(mut self, clear_stencil: u8) -> Self { - self.inner.clear_stencil = clear_stencil; - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct ColorTargetInfo { - inner: SDL_GPUColorTargetInfo, -} -impl ColorTargetInfo { - pub fn with_texture(mut self, texture: &Texture) -> Self { - self.inner.texture = texture.raw(); - self - } - pub fn with_load_op(mut self, value: LoadOp) -> Self { - self.inner.load_op = - unsafe { std::mem::transmute::<_, sys::gpu::SDL_GPULoadOp>(value as u32) }; - self - } - pub fn with_store_op(mut self, value: StoreOp) -> Self { - self.inner.store_op = - unsafe { std::mem::transmute::<_, sys::gpu::SDL_GPUStoreOp>(value as u32) }; - self - } - pub fn with_clear_color(mut self, value: Color) -> Self { - self.inner.clear_color.r = (value.r as f32) / 255.0; - self.inner.clear_color.g = (value.g as f32) / 255.0; - self.inner.clear_color.b = (value.b as f32) / 255.0; - self.inner.clear_color.a = (value.a as f32) / 255.0; - self - } -} - -pub struct RenderPass { - pub(super) inner: *mut SDL_GPURenderPass, -} -impl RenderPass { - #[inline] - pub fn raw(&self) -> *mut SDL_GPURenderPass { - self.inner - } - - #[doc(alias = "SDL_BindGPUGraphicsPipeline")] - pub fn bind_graphics_pipeline(&self, pipeline: &GraphicsPipeline) { - unsafe { sys::gpu::SDL_BindGPUGraphicsPipeline(self.inner, pipeline.raw()) } - } - - #[doc(alias = "SDL_BindGPUVertexBuffers")] - pub fn bind_vertex_buffers(&self, first_slot: u32, bindings: &[BufferBinding]) { - unsafe { - SDL_BindGPUVertexBuffers( - self.raw(), - first_slot, - bindings.as_ptr() as *mut SDL_GPUBufferBinding, - bindings.len() as u32, - ) - } - } - - #[doc(alias = "SDL_BindGPUVertexStorageBuffers")] - pub fn bind_vertex_storage_buffers(&self, first_slot: u32, storage_buffers: &[Buffer]) { - let buffer_handles = storage_buffers.iter().map(|x| x.raw()).collect::>(); - unsafe { - sys::gpu::SDL_BindGPUVertexStorageBuffers( - self.inner, - first_slot, - buffer_handles.as_ptr(), - buffer_handles.len() as u32, - ) - } - } - - #[doc(alias = "SDL_BindGPUVertexStorageTextures")] - pub fn bind_vertex_storage_textures(&self, first_slot: u32, storage_textures: &[Texture]) { - let texture_handles = storage_textures.iter().map(|x| x.raw()).collect::>(); - unsafe { - sys::gpu::SDL_BindGPUVertexStorageTextures( - self.inner, - first_slot, - texture_handles.as_ptr(), - texture_handles.len() as u32, - ) - } - } - - #[doc(alias = "SDL_BindGPUIndexBuffer")] - pub fn bind_index_buffer(&self, binding: &BufferBinding, index_element_size: IndexElementSize) { - unsafe { - SDL_BindGPUIndexBuffer( - self.raw(), - &binding.inner, - SDL_GPUIndexElementSize(index_element_size as i32), - ) - } - } - - #[doc(alias = "SDL_BindGPUFragmentSamplers")] - pub fn bind_fragment_samplers(&self, first_slot: u32, bindings: &[TextureSamplerBinding]) { - unsafe { - SDL_BindGPUFragmentSamplers( - self.raw(), - first_slot, - bindings.as_ptr() as *const SDL_GPUTextureSamplerBinding, - bindings.len() as u32, - ); - } - } - - #[doc(alias = "SDL_BindGPUFragmentStorageBuffers")] - pub fn bind_fragment_storage_buffers(&self, first_slot: u32, storage_buffers: &[Buffer]) { - let buffer_handles = storage_buffers.iter().map(|x| x.raw()).collect::>(); - unsafe { - sys::gpu::SDL_BindGPUFragmentStorageBuffers( - self.inner, - first_slot, - buffer_handles.as_ptr(), - buffer_handles.len() as u32, - ) - } - } - - #[doc(alias = "SDL_BindGPUFragmentStorageTextures")] - pub fn bind_fragment_storage_textures(&self, first_slot: u32, storage_textures: &[Texture]) { - let texture_handles = storage_textures.iter().map(|x| x.raw()).collect::>(); - unsafe { - sys::gpu::SDL_BindGPUFragmentStorageTextures( - self.inner, - first_slot, - texture_handles.as_ptr(), - texture_handles.len() as u32, - ) - } - } - - #[doc(alias = "SDL_DrawGPUIndexedPrimitives")] - pub fn draw_indexed_primitives( - &self, - num_indices: u32, - num_instances: u32, - first_index: u32, - vertex_offset: i32, - first_instance: u32, - ) { - unsafe { - SDL_DrawGPUIndexedPrimitives( - self.raw(), - num_indices, - num_instances, - first_index, - vertex_offset, - first_instance, - ); - } - } - - #[doc(alias = "SDL_DrawGPUPrimitives")] - pub fn draw_primitives( - &self, - num_vertices: usize, - num_instances: usize, - first_vertex: usize, - first_instance: usize, - ) { - unsafe { - sys::gpu::SDL_DrawGPUPrimitives( - self.inner, - num_vertices as u32, - num_instances as u32, - first_vertex as u32, - first_instance as u32, - ); - } - } -} - -pub struct CopyPass { - pub(super) inner: *mut SDL_GPUCopyPass, -} -impl CopyPass { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUCopyPass { - self.inner - } - - #[doc(alias = "SDL_UploadToGPUBuffer")] - pub fn upload_to_gpu_buffer( - &self, - transfer_buf_location: TransferBufferLocation, - buffer_region: BufferRegion, - cycle: bool, - ) { - unsafe { - SDL_UploadToGPUBuffer( - self.raw(), - &transfer_buf_location.inner, - &buffer_region.inner, - cycle, - ) - } - } - - #[doc(alias = "SDL_UploadToGPUTexture")] - pub fn upload_to_gpu_texture( - &self, - source: TextureTransferInfo, - destination: TextureRegion, - cycle: bool, - ) { - unsafe { SDL_UploadToGPUTexture(self.raw(), &source.inner, &destination.inner, cycle) } - } -} - -pub struct ComputePass { - pub(super) inner: *mut SDL_GPUComputePass, -} -impl ComputePass { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUComputePass { - self.inner - } - - #[doc(alias = "SDL_BindGPUComputePipeline")] - pub fn bind_compute_pipeline(&self, pipeline: &ComputePipeline) { - unsafe { sys::gpu::SDL_BindGPUComputePipeline(self.inner, pipeline.raw()) } - } - - #[doc(alias = "SDL_BindGPUComputeStorageBuffers")] - pub fn bind_compute_storage_buffers(&self, first_slot: u32, storage_buffers: &[Buffer]) { - let buffer_handles = storage_buffers.iter().map(|x| x.raw()).collect::>(); - unsafe { - sys::gpu::SDL_BindGPUComputeStorageBuffers( - self.inner, - first_slot, - buffer_handles.as_ptr(), - buffer_handles.len() as u32, - ) - } - } - - #[doc(alias = "SDL_BindGPUComputeStorageTextures")] - pub fn bind_compute_storage_textures(&self, first_slot: u32, storage_textures: &[Texture]) { - let texture_handles = storage_textures.iter().map(|x| x.raw()).collect::>(); - unsafe { - sys::gpu::SDL_BindGPUComputeStorageTextures( - self.inner, - first_slot, - texture_handles.as_ptr(), - texture_handles.len() as u32, - ) - } - } - - #[doc(alias = "SDL_DispatchGPUCompute")] - pub fn dispatch(&self, groupcount_x: u32, groupcount_y: u32, groupcount_z: u32) { - unsafe { - sys::gpu::SDL_DispatchGPUCompute(self.inner, groupcount_x, groupcount_y, groupcount_z) - } - } -} diff --git a/src/sdl3/gpu/pipeline.rs b/src/sdl3/gpu/pipeline.rs deleted file mode 100644 index 12b659ce..00000000 --- a/src/sdl3/gpu/pipeline.rs +++ /dev/null @@ -1,600 +0,0 @@ -use crate::{ - get_error, - gpu::{ - device::WeakDevice, BlendFactor, BlendOp, ColorComponentFlags, CompareOp, CullMode, Device, - FillMode, FrontFace, PrimitiveType, Shader, StencilOp, TextureFormat, - VertexBufferDescription, VertexElementFormat, - }, - sys, Error, -}; -use std::{ffi::CStr, sync::Arc}; -use sys::gpu::{ - SDL_GPUBlendFactor, SDL_GPUBlendOp, SDL_GPUColorTargetBlendState, - SDL_GPUColorTargetDescription, SDL_GPUCompareOp, SDL_GPUComputePipeline, - SDL_GPUComputePipelineCreateInfo, SDL_GPUCullMode, SDL_GPUDepthStencilState, SDL_GPUFillMode, - SDL_GPUFrontFace, SDL_GPUGraphicsPipeline, SDL_GPUGraphicsPipelineCreateInfo, - SDL_GPUGraphicsPipelineTargetInfo, SDL_GPUPrimitiveType, SDL_GPURasterizerState, - SDL_GPUStencilOp, SDL_GPUStencilOpState, SDL_GPUStorageBufferReadWriteBinding, - SDL_GPUStorageTextureReadWriteBinding, SDL_GPUTextureFormat, SDL_GPUVertexAttribute, - SDL_GPUVertexBufferDescription, SDL_GPUVertexInputState, SDL_ReleaseGPUComputePipeline, - SDL_ReleaseGPUGraphicsPipeline, -}; - -use super::{Buffer, ShaderFormat, Texture}; - -#[derive(Default)] -pub struct GraphicsPipelineTargetInfo { - inner: SDL_GPUGraphicsPipelineTargetInfo, -} -impl GraphicsPipelineTargetInfo { - pub fn new() -> Self { - Default::default() - } - - /// A pointer to an array of color target descriptions. - pub fn with_color_target_descriptions(mut self, value: &[ColorTargetDescription]) -> Self { - self.inner.color_target_descriptions = - value.as_ptr() as *const SDL_GPUColorTargetDescription; - self.inner.num_color_targets = value.len() as u32; - self - } - - /// The pixel format of the depth-stencil target. Ignored if has_depth_stencil_target is false. - pub fn with_depth_stencil_format(mut self, value: TextureFormat) -> Self { - self.inner.depth_stencil_format = SDL_GPUTextureFormat(value as i32); - self - } - - /// true specifies that the pipeline uses a depth-stencil target. - pub fn with_has_depth_stencil_target(mut self, value: bool) -> Self { - self.inner.has_depth_stencil_target = value; - self - } -} - -#[repr(C)] -pub struct GraphicsPipelineBuilder<'a> { - device: &'a Device, - inner: SDL_GPUGraphicsPipelineCreateInfo, -} -impl<'a> GraphicsPipelineBuilder<'a> { - pub(super) fn new(device: &'a Device) -> Self { - Self { - device, - inner: Default::default(), - } - } - - pub fn with_fragment_shader(mut self, value: &'a Shader) -> Self { - self.inner.fragment_shader = value.raw(); - self - } - pub fn with_vertex_shader(mut self, value: &'a Shader) -> Self { - self.inner.vertex_shader = value.raw(); - self - } - pub fn with_primitive_type(mut self, value: PrimitiveType) -> Self { - self.inner.primitive_type = SDL_GPUPrimitiveType(value as i32); - self - } - - /// Whether polygons will be filled in or drawn as lines. - /// - /// Note: this will override the value set in `with_rasterizer_state` if called after. - pub fn with_fill_mode(mut self, value: FillMode) -> Self { - self.inner.rasterizer_state.fill_mode = SDL_GPUFillMode(value as i32); - self - } - - /// Sets the parameters of the graphics pipeline rasterizer state. - /// - /// Note: this will override the value set in `with_fill_mode` if called after. - pub fn with_rasterizer_state(mut self, value: RasterizerState) -> Self { - self.inner.rasterizer_state = value.inner; - self - } - - pub fn with_depth_stencil_state(mut self, value: DepthStencilState) -> Self { - self.inner.depth_stencil_state = value.inner; - self - } - - pub fn with_vertex_input_state(mut self, value: VertexInputState) -> Self { - self.inner.vertex_input_state = value.inner; - self - } - - pub fn with_target_info(mut self, value: GraphicsPipelineTargetInfo) -> Self { - self.inner.target_info = value.inner; - self - } - - pub fn build(self) -> Result { - let raw_pipeline = - unsafe { sys::gpu::SDL_CreateGPUGraphicsPipeline(self.device.raw(), &self.inner) }; - if raw_pipeline.is_null() { - Err(get_error()) - } else { - Ok(GraphicsPipeline { - inner: Arc::new(GraphicsPipelineContainer { - raw: raw_pipeline, - device: self.device.weak(), - }), - }) - } - } -} - -/// Manages the raw `SDL_GPUGraphicsPipeline` pointer and releases it on drop -struct GraphicsPipelineContainer { - raw: *mut SDL_GPUGraphicsPipeline, - device: WeakDevice, -} -impl Drop for GraphicsPipelineContainer { - #[doc(alias = "SDL_ReleaseGPUGraphicsPipeline")] - fn drop(&mut self) { - if let Some(device) = self.device.upgrade() { - unsafe { SDL_ReleaseGPUGraphicsPipeline(device.raw(), self.raw) } - } - } -} - -#[derive(Clone)] -pub struct GraphicsPipeline { - inner: Arc, -} -impl GraphicsPipeline { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUGraphicsPipeline { - self.inner.raw - } -} - -#[repr(C)] -#[derive(Clone, Default)] -pub struct VertexAttribute { - inner: SDL_GPUVertexAttribute, -} -impl VertexAttribute { - pub fn new() -> Self { - Default::default() - } - - /// The shader input location index. - pub fn with_location(mut self, value: u32) -> Self { - self.inner.location = value; - self - } - - /// The binding slot of the associated vertex buffer. - pub fn with_buffer_slot(mut self, value: u32) -> Self { - self.inner.buffer_slot = value; - self - } - - /// The size and type of the attribute data. - pub fn with_format(mut self, value: VertexElementFormat) -> Self { - self.inner.format = unsafe { std::mem::transmute(value as u32) }; - self - } - - /// The byte offset of this attribute relative to the start of the vertex element. - pub fn with_offset(mut self, value: u32) -> Self { - self.inner.offset = value; - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct VertexInputState { - inner: SDL_GPUVertexInputState, -} -impl VertexInputState { - pub fn new() -> Self { - Default::default() - } - - pub fn with_vertex_buffer_descriptions(mut self, value: &[VertexBufferDescription]) -> Self { - self.inner.vertex_buffer_descriptions = - value.as_ptr() as *const SDL_GPUVertexBufferDescription; - self.inner.num_vertex_buffers = value.len() as u32; - self - } - - pub fn with_vertex_attributes(mut self, value: &[VertexAttribute]) -> Self { - self.inner.vertex_attributes = value.as_ptr() as *const SDL_GPUVertexAttribute; - self.inner.num_vertex_attributes = value.len() as u32; - self - } -} - -#[derive(Default)] -pub struct ColorTargetBlendState { - inner: SDL_GPUColorTargetBlendState, -} -impl ColorTargetBlendState { - pub fn new() -> Self { - Self::default() - } - - /// The value to be multiplied by the source RGB value. - pub fn with_src_color_blendfactor(mut self, blend_factor: BlendFactor) -> Self { - self.inner.src_color_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); - self - } - - /// The value to be multiplied by the destination RGB value. - pub fn with_dst_color_blendfactor(mut self, blend_factor: BlendFactor) -> Self { - self.inner.dst_color_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); - self - } - - /// The blend operation for the RGB components. - pub fn with_color_blend_op(mut self, blend_op: BlendOp) -> Self { - self.inner.color_blend_op = SDL_GPUBlendOp(blend_op as i32); - self - } - - /// The value to be multiplied by the source alpha. - pub fn with_src_alpha_blendfactor(mut self, blend_factor: BlendFactor) -> Self { - self.inner.src_alpha_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); - self - } - - /// The value to be multiplied by the destination alpha. - pub fn with_dst_alpha_blendfactor(mut self, blend_factor: BlendFactor) -> Self { - self.inner.dst_alpha_blendfactor = SDL_GPUBlendFactor(blend_factor as i32); - self - } - - /// The blend operation for the alpha component. - pub fn with_alpha_blend_op(mut self, blend_op: BlendOp) -> Self { - self.inner.alpha_blend_op = SDL_GPUBlendOp(blend_op as i32); - self - } - - /// A bitmask specifying which of the RGBA components are enabled for writing. Writes to all channels if enable_color_write_mask is false. - pub fn with_color_write_mask(mut self, flags: ColorComponentFlags) -> Self { - self.inner.color_write_mask = flags as u8; - self - } - - /// Whether blending is enabled for the color target. - pub fn with_enable_blend(mut self, enable: bool) -> Self { - self.inner.enable_blend = enable; - self - } - - /// Whether the color write mask is enabled. - pub fn with_enable_color_write_mask(mut self, enable: bool) -> Self { - self.inner.enable_color_write_mask = enable; - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct ColorTargetDescription { - inner: SDL_GPUColorTargetDescription, -} -impl ColorTargetDescription { - pub fn new() -> Self { - Self::default() - } - - /// The pixel format of the texture to be used as a color target. - pub fn with_format(mut self, value: TextureFormat) -> Self { - self.inner.format = SDL_GPUTextureFormat(value as i32); - self - } - - /// The blend state to be used for the color target. - pub fn with_blend_state(mut self, value: ColorTargetBlendState) -> Self { - self.inner.blend_state = value.inner; - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct RasterizerState { - inner: SDL_GPURasterizerState, -} -impl RasterizerState { - pub fn new() -> Self { - Default::default() - } - - /// Whether polygons will be filled in or drawn as lines. - pub fn with_fill_mode(mut self, fill_mode: FillMode) -> Self { - self.inner.fill_mode = SDL_GPUFillMode(fill_mode as i32); - self - } - - /// The facing direction in which triangles will be culled. - pub fn with_cull_mode(mut self, cull_mode: CullMode) -> Self { - self.inner.cull_mode = SDL_GPUCullMode(cull_mode as i32); - self - } - - /// The vertex winding that will cause a triangle to be determined as front-facing. - pub fn with_front_face(mut self, front_face: FrontFace) -> Self { - self.inner.front_face = SDL_GPUFrontFace(front_face as i32); - self - } - - /// A scalar factor controlling the depth value added to each fragment. - pub fn with_depth_bias_constant_factor(mut self, value: f32) -> Self { - self.inner.depth_bias_constant_factor = value; - self - } - - /// The maximum depth bias of a fragment. - pub fn with_depth_bias_clamp(mut self, value: f32) -> Self { - self.inner.depth_bias_clamp = value; - self - } - - /// A scalar factor applied to a fragment's slope in depth calculations. - pub fn with_depth_slope_factor(mut self, value: f32) -> Self { - self.inner.depth_bias_slope_factor = value; - self - } - - /// True to bias fragment depth values. - pub fn with_enable_depth_bias(mut self, value: bool) -> Self { - self.inner.enable_depth_bias = value; - self - } - - /// True to enable depth clip, false to enable depth clamp. - pub fn with_enable_depth_clip(mut self, value: bool) -> Self { - self.inner.enable_depth_clip = value; - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct StencilOpState { - inner: SDL_GPUStencilOpState, -} -impl StencilOpState { - pub fn new() -> Self { - Default::default() - } - - /// The action performed on samples that fail the stencil test. - pub fn with_fail_op(mut self, value: StencilOp) -> Self { - self.inner.fail_op = SDL_GPUStencilOp(value as i32); - self - } - - /// The action performed on samples that pass the depth and stencil tests. - pub fn with_pass_op(mut self, value: StencilOp) -> Self { - self.inner.pass_op = SDL_GPUStencilOp(value as i32); - self - } - - /// The action performed on samples that pass the stencil test and fail the depth test. - pub fn with_depth_fail_op(mut self, value: StencilOp) -> Self { - self.inner.depth_fail_op = SDL_GPUStencilOp(value as i32); - self - } - - /// The comparison operator used in the stencil test. - pub fn compare_op(mut self, value: CompareOp) -> Self { - self.inner.compare_op = SDL_GPUCompareOp(value as i32); - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct DepthStencilState { - inner: SDL_GPUDepthStencilState, -} -impl DepthStencilState { - pub fn new() -> Self { - Default::default() - } - - /// The comparison operator used for depth testing. - pub fn with_compare_op(mut self, value: CompareOp) -> Self { - self.inner.compare_op = SDL_GPUCompareOp(value as i32); - self - } - - /// The stencil op state for back-facing triangles. - pub fn with_back_stencil_state(mut self, value: StencilOpState) -> Self { - self.inner.back_stencil_state = value.inner; - self - } - - /// The stencil op state for front-facing triangles. - pub fn with_front_stencil_state(mut self, value: StencilOpState) -> Self { - self.inner.front_stencil_state = value.inner; - self - } - - /// Selects the bits of the stencil values participating in the stencil test. - pub fn with_compare_mask(mut self, value: u8) -> Self { - self.inner.compare_mask = value; - self - } - - /// Selects the bits of the stencil values updated by the stencil test. - pub fn with_write_mask(mut self, value: u8) -> Self { - self.inner.write_mask = value; - self - } - - /// True enables the depth test. - pub fn with_enable_depth_test(mut self, value: bool) -> Self { - self.inner.enable_depth_test = value; - self - } - - /// True enables depth writes. - pub fn with_enable_depth_write(mut self, value: bool) -> Self { - self.inner.enable_depth_write = value; - self - } - - /// True enables the stencil test. - pub fn with_enable_stencil_test(mut self, value: bool) -> Self { - self.inner.enable_stencil_test = value; - self - } -} - -#[repr(C)] -pub struct ComputePipelineBuilder<'a> { - device: &'a Device, - inner: SDL_GPUComputePipelineCreateInfo, -} -impl<'a> ComputePipelineBuilder<'a> { - pub(super) fn new(device: &'a Device) -> Self { - Self { - device, - inner: Default::default(), - } - } - - pub fn with_code(mut self, fmt: ShaderFormat, code: &'a [u8]) -> Self { - self.inner.format = fmt as u32; - self.inner.code = code.as_ptr(); - self.inner.code_size = code.len(); - self - } - - pub fn with_entrypoint(mut self, entry_point: &'a CStr) -> Self { - self.inner.entrypoint = entry_point.as_ptr(); - self - } - - pub fn with_readonly_storage_textures(mut self, value: u32) -> Self { - self.inner.num_readonly_storage_textures = value; - self - } - - pub fn with_readonly_storage_buffers(mut self, value: u32) -> Self { - self.inner.num_readonly_storage_buffers = value; - self - } - - pub fn with_readwrite_storage_textures(mut self, value: u32) -> Self { - self.inner.num_readwrite_storage_textures = value; - self - } - - pub fn with_readwrite_storage_buffers(mut self, value: u32) -> Self { - self.inner.num_readwrite_storage_buffers = value; - self - } - - pub fn with_uniform_buffers(mut self, value: u32) -> Self { - self.inner.num_uniform_buffers = value; - self - } - - pub fn with_thread_count(mut self, x: u32, y: u32, z: u32) -> Self { - self.inner.threadcount_x = x; - self.inner.threadcount_y = y; - self.inner.threadcount_z = z; - self - } - - pub fn build(self) -> Result { - let raw_pipeline = - unsafe { sys::gpu::SDL_CreateGPUComputePipeline(self.device.raw(), &self.inner) }; - if raw_pipeline.is_null() { - Err(get_error()) - } else { - Ok(ComputePipeline { - inner: Arc::new(ComputePipelineContainer { - raw: raw_pipeline, - device: self.device.weak(), - }), - }) - } - } -} - -/// Manages the raw `SDL_GPUComputePipeline` pointer and releases it on drop -struct ComputePipelineContainer { - raw: *mut SDL_GPUComputePipeline, - device: WeakDevice, -} -impl Drop for ComputePipelineContainer { - #[doc(alias = "SDL_ReleaseGPUComputePipeline")] - fn drop(&mut self) { - if let Some(device) = self.device.upgrade() { - unsafe { SDL_ReleaseGPUComputePipeline(device.raw(), self.raw) } - } - } -} - -#[derive(Clone)] -pub struct ComputePipeline { - inner: Arc, -} -impl ComputePipeline { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUComputePipeline { - self.inner.raw - } -} - -#[repr(C)] -#[derive(Default)] -pub struct StorageTextureReadWriteBinding { - inner: SDL_GPUStorageTextureReadWriteBinding, -} -impl StorageTextureReadWriteBinding { - pub fn new() -> Self { - Default::default() - } - - pub fn with_texture(mut self, texture: &Texture) -> Self { - self.inner.texture = texture.raw(); - self - } - - pub fn with_mip_level(mut self, mip_level: u32) -> Self { - self.inner.mip_level = mip_level; - self - } - - pub fn with_layer(mut self, layer: u32) -> Self { - self.inner.layer = layer; - self - } - - pub fn with_cycle(mut self, cycle: bool) -> Self { - self.inner.cycle = cycle; - self - } -} - -#[repr(C)] -#[derive(Default)] -pub struct StorageBufferReadWriteBinding { - inner: SDL_GPUStorageBufferReadWriteBinding, -} -impl StorageBufferReadWriteBinding { - pub fn new() -> Self { - Default::default() - } - - pub fn with_buffer(mut self, buffer: &Buffer) -> Self { - self.inner.buffer = buffer.raw(); - self - } - - pub fn with_cycle(mut self, cycle: bool) -> Self { - self.inner.cycle = cycle; - self - } -} diff --git a/src/sdl3/gpu/resource/builders.rs b/src/sdl3/gpu/resource/builders.rs new file mode 100644 index 00000000..4621b676 --- /dev/null +++ b/src/sdl3/gpu/resource/builders.rs @@ -0,0 +1,310 @@ +//! Defines builders for various GPU-resources. +//! +//! + +use std::{ffi::CStr, marker::PhantomData}; + +use sys::gpu::{ + SDL_GPUBufferCreateInfo, SDL_GPUComputePipelineCreateInfo, SDL_GPUFillMode, + SDL_GPUGraphicsPipelineCreateInfo, SDL_GPUPrimitiveType, SDL_GPUShaderCreateInfo, + SDL_GPUTransferBufferCreateInfo, +}; + +use crate::gpu::{SamplerCreateInfo, TextureCreateInfo}; +use crate::Error; + +use super::super::{ + BufferUsageFlags, ComputePipeline, DepthStencilState, FillMode, GraphicsPipeline, + GraphicsPipelineTargetInfo, PrimitiveType, RasterizerState, Shader, ShaderFormat, ShaderStage, + TransferBuffer, TransferBufferUsage, VertexInputState, +}; + +impl Device { + #[doc(alias = "SDL_CreateGPUShader")] + pub fn create_shader(&self) -> ShaderBuilder { + ShaderBuilder::new(self) + } + + #[doc(alias = "SDL_CreateGPUBuffer")] + pub fn create_buffer(&self) -> BufferBuilder { + BufferBuilder::new(self) + } + + #[doc(alias = "SDL_CreateGPUTransferBuffer")] + pub fn create_transfer_buffer(&self) -> TransferBufferBuilder { + TransferBufferBuilder::new(self) + } + + #[doc(alias = "SDL_CreateGPUSampler")] + pub fn create_sampler<'gpu>( + &'gpu self, + create_info: SamplerCreateInfo, + ) -> Result, Error> { + Owned::new(self, &create_info.inner, ()) + } + + #[doc(alias = "SDL_CreateGPUGraphicsPipeline")] + pub fn create_graphics_pipeline<'gpu, 'a>(&'gpu self) -> GraphicsPipelineBuilder<'gpu, 'a> { + GraphicsPipelineBuilder::new(self) + } + + #[doc(alias = "SDL_CreateGPUComputePipeline")] + pub fn create_compute_pipeline<'gpu, 'a>(&'gpu self) -> ComputePipelineBuilder<'gpu, 'a> { + ComputePipelineBuilder::new(self) + } + + #[doc(alias = "SDL_CreateGPUTexture")] + pub fn create_texture<'gpu>( + &'gpu self, + create_info: &TextureCreateInfo, + ) -> Result, Error> { + Owned::new( + self, + &create_info.inner, + (create_info.inner.width, create_info.inner.height), + ) + } +} + +use super::{Buffer, Device, Owned, Sampler, Texture}; +#[repr(C)] +pub struct ComputePipelineBuilder<'gpu, 'builder> { + device: &'gpu Device, + inner: SDL_GPUComputePipelineCreateInfo, + _marker: PhantomData<&'builder Shader>, +} +impl<'gpu, 'builder> ComputePipelineBuilder<'gpu, 'builder> { + pub(in crate::gpu) fn new(device: &'gpu Device) -> Self { + Self { + device, + inner: Default::default(), + _marker: PhantomData, + } + } + + pub fn with_code(mut self, fmt: ShaderFormat, code: &'builder [u8]) -> Self { + self.inner.format = fmt.0; + self.inner.code = code.as_ptr(); + self.inner.code_size = code.len(); + self + } + + pub fn with_entrypoint(mut self, entry_point: &'builder CStr) -> Self { + self.inner.entrypoint = entry_point.as_ptr(); + self + } + + pub fn with_readonly_storage_textures(mut self, value: u32) -> Self { + self.inner.num_readonly_storage_textures = value; + self + } + + pub fn with_readonly_storage_buffers(mut self, value: u32) -> Self { + self.inner.num_readonly_storage_buffers = value; + self + } + + pub fn with_readwrite_storage_textures(mut self, value: u32) -> Self { + self.inner.num_readwrite_storage_textures = value; + self + } + + pub fn with_readwrite_storage_buffers(mut self, value: u32) -> Self { + self.inner.num_readwrite_storage_buffers = value; + self + } + + pub fn with_uniform_buffers(mut self, value: u32) -> Self { + self.inner.num_uniform_buffers = value; + self + } + + pub fn with_thread_count(mut self, x: u32, y: u32, z: u32) -> Self { + self.inner.threadcount_x = x; + self.inner.threadcount_y = y; + self.inner.threadcount_z = z; + self + } + + pub fn build(self) -> Result, Error> { + Owned::new(self.device, &self.inner, ()) + } +} + +pub struct ShaderBuilder<'builder, 'gpu> { + device: &'gpu Device, + inner: SDL_GPUShaderCreateInfo, + _marker: PhantomData<&'builder [u8]>, +} + +impl<'gpu, 'builder> ShaderBuilder<'builder, 'gpu> { + pub(in crate::gpu) fn new(device: &'gpu Device) -> Self { + Self { + device, + inner: Default::default(), + _marker: PhantomData, + } + } + + pub fn with_samplers(mut self, value: u32) -> Self { + self.inner.num_samplers = value; + self + } + + pub fn with_storage_buffers(mut self, value: u32) -> Self { + self.inner.num_storage_buffers = value; + self + } + + pub fn with_storage_textures(mut self, value: u32) -> Self { + self.inner.num_storage_textures = value; + self + } + + pub fn with_uniform_buffers(mut self, value: u32) -> Self { + self.inner.num_uniform_buffers = value; + self + } + + pub fn with_code( + mut self, + fmt: ShaderFormat, + code: &'builder [u8], + stage: ShaderStage, + ) -> Self { + self.inner.format = fmt.0; + self.inner.code = code.as_ptr(); + self.inner.code_size = code.len() as usize; + self.inner.stage = unsafe { std::mem::transmute(stage as u32) }; + self + } + pub fn with_entrypoint(mut self, entry_point: &'gpu CStr) -> Self { + self.inner.entrypoint = entry_point.as_ptr(); + self + } + pub fn build(self) -> Result, Error> { + Owned::new(self.device, &self.inner, ()) + } +} + +pub struct TransferBufferBuilder<'gpu> { + device: &'gpu Device, + inner: SDL_GPUTransferBufferCreateInfo, +} +impl<'gpu> TransferBufferBuilder<'gpu> { + pub(in crate::gpu) fn new(device: &'gpu Device) -> Self { + Self { + device, + inner: Default::default(), + } + } + + /// How the buffer will be used. + pub fn with_usage(mut self, value: TransferBufferUsage) -> Self { + self.inner.usage = value; + self + } + + /// Desired size of the buffer in bytes. + pub fn with_size(mut self, value: u32) -> Self { + self.inner.size = value; + self + } + + pub fn build(self) -> Result, Error> { + Owned::new(self.device, &self.inner, self.inner.size) + } +} + +pub struct BufferBuilder<'gpu> { + device: &'gpu Device, + inner: SDL_GPUBufferCreateInfo, +} +impl<'gpu> BufferBuilder<'gpu> { + pub(in crate::gpu) fn new(device: &'gpu Device) -> Self { + Self { + device, + inner: Default::default(), + } + } + + pub fn with_usage(mut self, value: BufferUsageFlags) -> Self { + self.inner.usage = value.0; + self + } + + pub fn with_size(mut self, value: u32) -> Self { + self.inner.size = value; + self + } + + pub fn build(self) -> Result, Error> { + Owned::new(self.device, &self.inner, self.inner.size) + } +} + +#[derive(Copy, Clone)] +#[repr(C)] +pub struct GraphicsPipelineBuilder<'gpu, 'builder> { + device: &'gpu Device, + inner: SDL_GPUGraphicsPipelineCreateInfo, + _marker: PhantomData<(&'builder Shader, GraphicsPipelineTargetInfo<'builder>)>, +} + +impl<'gpu, 'builder> GraphicsPipelineBuilder<'gpu, 'builder> { + pub(in crate::gpu) fn new(device: &'gpu Device) -> Self { + Self { + device, + inner: Default::default(), + _marker: PhantomData, + } + } + + pub fn with_fragment_shader(mut self, value: &'builder Shader) -> Self { + self.inner.fragment_shader = value.ll(); + self + } + pub fn with_vertex_shader(mut self, value: &'builder Shader) -> Self { + self.inner.vertex_shader = value.ll(); + self + } + pub fn with_primitive_type(mut self, value: PrimitiveType) -> Self { + self.inner.primitive_type = SDL_GPUPrimitiveType(value as i32); + self + } + + /// Whether polygons will be filled in or drawn as lines. + /// + /// Note: this will override the value set in `with_rasterizer_state` if called after. + pub fn with_fill_mode(mut self, value: FillMode) -> Self { + self.inner.rasterizer_state.fill_mode = SDL_GPUFillMode(value as i32); + self + } + + /// Sets the parameters of the graphics pipeline rasterizer state. + /// + /// Note: this will override the value set in `with_fill_mode` if called after. + pub fn with_rasterizer_state(mut self, value: RasterizerState) -> Self { + self.inner.rasterizer_state = value.inner; + self + } + + pub fn with_depth_stencil_state(mut self, value: DepthStencilState) -> Self { + self.inner.depth_stencil_state = value.inner; + self + } + + pub fn with_vertex_input_state(mut self, value: VertexInputState<'builder>) -> Self { + self.inner.vertex_input_state = value.inner; + self + } + + pub fn with_target_info(mut self, value: GraphicsPipelineTargetInfo<'builder>) -> Self { + self.inner.target_info = value.inner; + self + } + + pub fn build(&self) -> Result, Error> { + Owned::new(self.device, &self.inner, ()) + } +} diff --git a/src/sdl3/gpu/resource/device.rs b/src/sdl3/gpu/resource/device.rs new file mode 100644 index 00000000..5e36fd18 --- /dev/null +++ b/src/sdl3/gpu/resource/device.rs @@ -0,0 +1,42 @@ +use std::{ops::Deref, ptr::NonNull}; + +use crate::{ + gpu::{util::nonnull_ext_or_get_error, Extern}, + Error, +}; + +use super::super::ShaderFormat; + +use sys::gpu::{SDL_CreateGPUDevice, SDL_DestroyGPUDevice, SDL_GPUDevice}; + +pub type Device = Extern; + +pub struct OwnedDevice { + raw: NonNull, +} + +impl OwnedDevice { + #[doc(alias = "SDL_CreateGPUDevice")] + pub fn new(flags: ShaderFormat, debug_mode: bool) -> Result { + let raw = nonnull_ext_or_get_error(unsafe { + SDL_CreateGPUDevice(flags.0, debug_mode, std::ptr::null()) + })? + .cast(); + Ok(Self { raw }) + } +} + +impl Drop for OwnedDevice { + #[doc(alias = "SDL_DestroyGPUDevice")] + fn drop(&mut self) { + unsafe { SDL_DestroyGPUDevice(self.ll()) } + } +} + +impl Deref for OwnedDevice { + type Target = Device; + + fn deref(&self) -> &Self::Target { + unsafe { self.raw.as_ref() } + } +} diff --git a/src/sdl3/gpu/resource/mod.rs b/src/sdl3/gpu/resource/mod.rs new file mode 100644 index 00000000..60e47b5d --- /dev/null +++ b/src/sdl3/gpu/resource/mod.rs @@ -0,0 +1,295 @@ +//! GPU-resources +//! +//! +mod builders; +use std::{marker::PhantomData, ptr::NonNull}; + +pub use builders::{ + BufferBuilder, ComputePipelineBuilder, GraphicsPipelineBuilder, ShaderBuilder, + TransferBufferBuilder, +}; + +mod device; +pub use device::Device; +pub use device::OwnedDevice; + +use sys::gpu::{ + SDL_GPUBufferRegion, SDL_GPUDevice, SDL_GPUStorageBufferReadWriteBinding, + SDL_GPUStorageTextureReadWriteBinding, SDL_GPUTransferBufferLocation, SDL_MapGPUTransferBuffer, + SDL_UnmapGPUTransferBuffer, +}; + +use crate::Error; +use crate::{get_error, gpu::BufferRegion}; + +use super::util::Defer; +use super::Extern; +use super::{ + StorageBufferReadWriteBinding, StorageTextureReadWriteBinding, TextureSamplerBinding, + TransferBufferLocation, +}; + +pub unsafe trait GpuCreate: GpuRelease { + type CreateInfo; + const CREATE: unsafe extern "C" fn( + *mut SDL_GPUDevice, + *const Self::CreateInfo, + ) -> *mut Self::SDLType; +} + +pub unsafe trait GpuRelease { + type SDLType; + const RELEASE: unsafe extern "C" fn(*mut SDL_GPUDevice, *mut Self::SDLType); + + // any additional state that `Owned` will keep + type ExtraState; +} + +pub struct Owned<'gpu, T: GpuRelease> { + raw: NonNull, + ctx: &'gpu Device, + extra: T::ExtraState, +} + +impl<'gpu, T: GpuCreate + GpuRelease> Owned<'gpu, T> { + pub(crate) fn new( + ctx: &'gpu Device, + info: &T::CreateInfo, + extra: T::ExtraState, + ) -> Result { + unsafe { + let raw: *mut T::SDLType = T::CREATE(ctx.ll(), info); + let raw: *mut T = raw.cast(); + if let Some(raw) = NonNull::new(raw) { + Ok(Owned { raw, ctx, extra }) + } else { + Err(get_error()) + } + } + } +} + +impl<'gpu, T: GpuRelease> ::core::ops::Deref for Owned<'gpu, T> { + type Target = Extern; + + fn deref(&self) -> &Self::Target { + unsafe { self.raw.cast().as_ref() } + } +} + +impl<'gpu, T: GpuRelease> Drop for Owned<'gpu, T> { + fn drop(&mut self) { + unsafe { + T::RELEASE(self.ctx.ll(), self.raw.as_ptr().cast()); + } + } +} + +macro_rules! gpu_resource { + ($rust_name:ident, $sdl_name:path, $info:path, $create:path, $release:path, $extra:ty) => { + const _: () = assert!(size_of::<$sdl_name>() == 0); + + pub type $rust_name = Extern<$sdl_name>; + unsafe impl GpuCreate for $rust_name { + type CreateInfo = $info; + + const CREATE: unsafe extern "C" fn( + *mut SDL_GPUDevice, + *const Self::CreateInfo, + ) -> *mut Self::SDLType = $create; + } + unsafe impl GpuRelease for $rust_name { + type SDLType = $sdl_name; + type ExtraState = $extra; + + const RELEASE: unsafe extern "C" fn(*mut SDL_GPUDevice, *mut Self::SDLType) = $release; + } + }; +} + +gpu_resource!( + ComputePipeline, + sys::gpu::SDL_GPUComputePipeline, + sys::gpu::SDL_GPUComputePipelineCreateInfo, + sys::gpu::SDL_CreateGPUComputePipeline, + sys::gpu::SDL_ReleaseGPUComputePipeline, + () +); + +gpu_resource!( + GraphicsPipeline, + sys::gpu::SDL_GPUGraphicsPipeline, + sys::gpu::SDL_GPUGraphicsPipelineCreateInfo, + sys::gpu::SDL_CreateGPUGraphicsPipeline, + sys::gpu::SDL_ReleaseGPUGraphicsPipeline, + () +); + +gpu_resource!( + Sampler, + sys::gpu::SDL_GPUSampler, + sys::gpu::SDL_GPUSamplerCreateInfo, + sys::gpu::SDL_CreateGPUSampler, + sys::gpu::SDL_ReleaseGPUSampler, + () +); + +gpu_resource!( + Shader, + sys::gpu::SDL_GPUShader, + sys::gpu::SDL_GPUShaderCreateInfo, + sys::gpu::SDL_CreateGPUShader, + sys::gpu::SDL_ReleaseGPUShader, + () +); + +gpu_resource!( + Texture, + sys::gpu::SDL_GPUTexture, + sys::gpu::SDL_GPUTextureCreateInfo, + sys::gpu::SDL_CreateGPUTexture, + sys::gpu::SDL_ReleaseGPUTexture, + (u32, u32) +); + +gpu_resource!( + TransferBuffer, + sys::gpu::SDL_GPUTransferBuffer, + sys::gpu::SDL_GPUTransferBufferCreateInfo, + sys::gpu::SDL_CreateGPUTransferBuffer, + sys::gpu::SDL_ReleaseGPUTransferBuffer, + u32 +); + +gpu_resource!( + Buffer, + sys::gpu::SDL_GPUBuffer, + sys::gpu::SDL_GPUBufferCreateInfo, + sys::gpu::SDL_CreateGPUBuffer, + sys::gpu::SDL_ReleaseGPUBuffer, + u32 +); + +impl<'a> Owned<'a, Texture> { + pub fn width(&self) -> u32 { + self.extra.0 + } + + pub fn height(&self) -> u32 { + self.extra.1 + } +} + +impl<'gpu> Owned<'gpu, Buffer> { + /// The length of this buffer in bytes. + pub fn len(&self) -> u32 { + self.extra + } +} + +impl Buffer { + pub fn get(&self, range: std::ops::Range) -> BufferRegion<'_> { + assert!(range.end >= range.start); + BufferRegion { + inner: SDL_GPUBufferRegion { + buffer: self.ll(), + offset: range.start, + size: range.end - range.start, + ..Default::default() + }, + _marker: PhantomData, + } + } + + /// Create a read-write binding that cycles this buffer when bound + pub fn cycled(&self) -> StorageBufferReadWriteBinding<'_> { + let mut inner = SDL_GPUStorageBufferReadWriteBinding::default(); + inner.cycle = true; + inner.buffer = self.ll(); + StorageBufferReadWriteBinding { + inner, + _marker: PhantomData, + } + } + + /// Create a read-write binding that refers to the current contents of the buffer + pub fn preserved(&self) -> StorageBufferReadWriteBinding<'_> { + let mut inner = SDL_GPUStorageBufferReadWriteBinding::default(); + inner.cycle = false; + inner.buffer = self.ll(); + StorageBufferReadWriteBinding { + inner, + _marker: PhantomData, + } + } +} + +impl Texture { + /// Create a read-write binding that cycles this texture when bound + pub fn cycled(&self) -> StorageTextureReadWriteBinding<'_> { + let mut inner = SDL_GPUStorageTextureReadWriteBinding::default(); + inner.cycle = true; + inner.texture = self.ll(); + StorageTextureReadWriteBinding { + inner, + _marker: PhantomData, + } + } + + /// Create a read-write binding that refers to the current contents of the texture + pub fn preserved(&self) -> StorageTextureReadWriteBinding<'_> { + let mut inner = SDL_GPUStorageTextureReadWriteBinding::default(); + inner.cycle = false; + inner.texture = self.ll(); + StorageTextureReadWriteBinding { + inner, + _marker: PhantomData, + } + } + + pub fn with_sampler<'a>(&'a self, sampler: &'a Sampler) -> TextureSamplerBinding<'a> { + let mut binding = TextureSamplerBinding::default(); + binding.inner.texture = self.ll(); + binding.inner.sampler = sampler.ll(); + binding + } +} + +impl TransferBuffer { + pub fn get<'a>(&'a self, from: std::ops::RangeFrom) -> TransferBufferLocation<'a> { + TransferBufferLocation { + inner: SDL_GPUTransferBufferLocation { + transfer_buffer: self.ll(), + offset: from.start, + ..Default::default() + }, + _marker: PhantomData, + } + } +} + +impl<'gpu> Owned<'gpu, TransferBuffer> { + #[doc(alias = "SDL_MapGPUTransferBuffer")] + pub fn mapped_mut( + &mut self, + cycle: bool, + f: impl for<'a> FnOnce(&'a mut [u8]) -> R, + ) -> Result { + unsafe { + let raw = SDL_MapGPUTransferBuffer(self.ctx.ll(), self.ll(), cycle); + if raw.is_null() { + return Err(get_error()); + } + + let bytes = std::slice::from_raw_parts_mut(raw as *mut u8, self.extra as usize); + + let _defer = Defer::new(|| SDL_UnmapGPUTransferBuffer(self.ctx.ll(), self.ll())); + + Ok(f(bytes)) + } + } + + pub fn len(&self) -> u32 { + self.extra + } +} diff --git a/src/sdl3/gpu/shader.rs b/src/sdl3/gpu/shader.rs deleted file mode 100644 index 10352111..00000000 --- a/src/sdl3/gpu/shader.rs +++ /dev/null @@ -1,89 +0,0 @@ -use crate::{ - get_error, - gpu::{Device, ShaderFormat, ShaderStage, WeakDevice}, - Error, -}; -use std::{ffi::CStr, sync::Arc}; -use sys::gpu::{SDL_GPUShader, SDL_GPUShaderCreateInfo}; - -/// Manages the raw `SDL_GPUShader` pointer and releases it on drop -struct ShaderContainer { - raw: *mut SDL_GPUShader, - device: WeakDevice, -} -impl Drop for ShaderContainer { - fn drop(&mut self) { - if let Some(device) = self.device.upgrade() { - unsafe { sys::gpu::SDL_ReleaseGPUShader(device.raw(), self.raw) } - } - } -} - -#[derive(Clone)] -pub struct Shader { - inner: Arc, -} -impl Shader { - #[inline] - pub fn raw(&self) -> *mut SDL_GPUShader { - self.inner.raw - } -} - -pub struct ShaderBuilder<'a> { - device: &'a Device, - inner: SDL_GPUShaderCreateInfo, -} -impl<'a> ShaderBuilder<'a> { - pub(super) fn new(device: &'a Device) -> Self { - Self { - device, - inner: Default::default(), - } - } - - pub fn with_samplers(mut self, value: u32) -> Self { - self.inner.num_samplers = value; - self - } - - pub fn with_storage_buffers(mut self, value: u32) -> Self { - self.inner.num_storage_buffers = value; - self - } - - pub fn with_storage_textures(mut self, value: u32) -> Self { - self.inner.num_storage_textures = value; - self - } - - pub fn with_uniform_buffers(mut self, value: u32) -> Self { - self.inner.num_uniform_buffers = value; - self - } - - pub fn with_code(mut self, fmt: ShaderFormat, code: &'a [u8], stage: ShaderStage) -> Self { - self.inner.format = fmt as u32; - self.inner.code = code.as_ptr(); - self.inner.code_size = code.len() as usize; - self.inner.stage = unsafe { std::mem::transmute(stage as u32) }; - self - } - pub fn with_entrypoint(mut self, entry_point: &'a CStr) -> Self { - self.inner.entrypoint = entry_point.as_ptr(); - self - } - pub fn build(self) -> Result { - let raw_shader = unsafe { sys::gpu::SDL_CreateGPUShader(self.device.raw(), &self.inner) }; - if !raw_shader.is_null() { - Ok(Shader { - inner: Arc::new(ShaderContainer { - raw: raw_shader, - device: self.device.weak(), - }), - }) - } else { - Err(get_error()) - } - } -} diff --git a/src/sdl3/gpu/texture.rs b/src/sdl3/gpu/texture.rs deleted file mode 100644 index 8c4493c3..00000000 --- a/src/sdl3/gpu/texture.rs +++ /dev/null @@ -1,397 +0,0 @@ -use crate::gpu::{ - CompareOp, Device, Filter, SampleCount, SamplerAddressMode, SamplerMipmapMode, TextureFormat, - TextureType, TextureUsage, TransferBuffer, WeakDevice, -}; -use std::{marker::PhantomData, sync::Arc}; -use sys::gpu::{ - SDL_GPUCompareOp, SDL_GPUFilter, SDL_GPUSampleCount, SDL_GPUSampler, SDL_GPUSamplerAddressMode, - SDL_GPUSamplerCreateInfo, SDL_GPUSamplerMipmapMode, SDL_GPUTexture, SDL_GPUTextureCreateInfo, - SDL_GPUTextureFormat, SDL_GPUTextureRegion, SDL_GPUTextureSamplerBinding, - SDL_GPUTextureTransferInfo, SDL_GPUTextureType, SDL_ReleaseGPUSampler, SDL_ReleaseGPUTexture, -}; - -#[derive(Default)] -pub struct TextureTransferInfo { - pub(super) inner: SDL_GPUTextureTransferInfo, -} -impl TextureTransferInfo { - pub fn new() -> Self { - Default::default() - } - - /// The transfer buffer used in the transfer operation. - pub fn with_transfer_buffer(mut self, buffer: &TransferBuffer) -> Self { - self.inner.transfer_buffer = buffer.raw(); - self - } - - /// The starting byte of the image data in the transfer buffer. - pub fn with_offset(mut self, offset: u32) -> Self { - self.inner.offset = offset; - self - } - - /// The number of pixels from one row to the next. - pub fn with_pixels_per_row(mut self, value: u32) -> Self { - self.inner.pixels_per_row = value; - self - } - - /// The number of rows from one layer/depth-slice to the next. - pub fn with_rows_per_layer(mut self, value: u32) -> Self { - self.inner.rows_per_layer = value; - self - } -} - -#[derive(Default)] -pub struct TextureRegion { - pub(super) inner: SDL_GPUTextureRegion, -} -impl TextureRegion { - pub fn new() -> Self { - Default::default() - } - - /// The texture used in the copy operation. - pub fn with_texture(mut self, texture: &Texture) -> Self { - self.inner.texture = texture.raw(); - self - } - - /// The mip level index to transfer. - pub fn with_mip_level(mut self, mip_level: u32) -> Self { - self.inner.mip_level = mip_level; - self - } - - /// The layer index to transfer. - pub fn with_layer(mut self, layer: u32) -> Self { - self.inner.layer = layer; - self - } - - /// The left offset of the region. - pub fn with_x(mut self, x: u32) -> Self { - self.inner.x = x; - self - } - - /// The top offset of the region. - pub fn with_y(mut self, y: u32) -> Self { - self.inner.y = y; - self - } - - /// The front offset of the region. - pub fn with_z(mut self, z: u32) -> Self { - self.inner.z = z; - self - } - - /// The width of the region. - pub fn with_width(mut self, width: u32) -> Self { - self.inner.w = width; - self - } - - /// The height of the region. - pub fn with_height(mut self, height: u32) -> Self { - self.inner.h = height; - self - } - - /// The depth of the region. - pub fn with_depth(mut self, depth: u32) -> Self { - self.inner.d = depth; - self - } -} - -#[derive(Default)] -pub struct SamplerCreateInfo { - pub(super) inner: SDL_GPUSamplerCreateInfo, -} -impl SamplerCreateInfo { - pub fn new() -> Self { - Default::default() - } - - /// The minification filter to apply to lookups. - pub fn with_min_filter(mut self, filter: Filter) -> Self { - self.inner.min_filter = SDL_GPUFilter(filter as i32); - self - } - - /// The magnification filter to apply to lookups. - pub fn with_mag_filter(mut self, filter: Filter) -> Self { - self.inner.mag_filter = SDL_GPUFilter(filter as i32); - self - } - - /// The mipmap filter to apply to lookups. - pub fn with_mipmap_mode(mut self, mode: SamplerMipmapMode) -> Self { - self.inner.mipmap_mode = SDL_GPUSamplerMipmapMode(mode as i32); - self - } - - /// The addressing mode for U coordinates outside [0, 1). - pub fn with_address_mode_u(mut self, mode: SamplerAddressMode) -> Self { - self.inner.address_mode_u = SDL_GPUSamplerAddressMode(mode as i32); - self - } - - /// The addressing mode for V coordinates outside [0, 1). - pub fn with_address_mode_v(mut self, mode: SamplerAddressMode) -> Self { - self.inner.address_mode_v = SDL_GPUSamplerAddressMode(mode as i32); - self - } - - /// The addressing mode for W coordinates outside [0, 1). - pub fn with_address_mode_w(mut self, mode: SamplerAddressMode) -> Self { - self.inner.address_mode_w = SDL_GPUSamplerAddressMode(mode as i32); - self - } - - /// The bias to be added to mipmap LOD calculation. - pub fn with_mip_lod_bias(mut self, value: f32) -> Self { - self.inner.mip_lod_bias = value; - self - } - - /// The anisotropy value clamp used by the sampler. If enable_anisotropy is false, this is ignored. - pub fn with_max_anisotropy(mut self, value: f32) -> Self { - self.inner.max_anisotropy = value; - self - } - - /// The comparison operator to apply to fetched data before filtering. - pub fn with_compare_op(mut self, value: CompareOp) -> Self { - self.inner.compare_op = SDL_GPUCompareOp(value as i32); - self - } - - /// Clamps the minimum of the computed LOD value. - pub fn with_min_lod(mut self, value: f32) -> Self { - self.inner.min_lod = value; - self - } - - /// Clamps the maximum of the computed LOD value. - pub fn with_max_lod(mut self, value: f32) -> Self { - self.inner.max_lod = value; - self - } - - /// True to enable anisotropic filtering. - pub fn with_enable_anisotropy(mut self, enable: bool) -> Self { - self.inner.enable_anisotropy = enable; - self - } - - /// True to enable comparison against a reference value during lookups. - pub fn with_enable_compare(mut self, enable: bool) -> Self { - self.inner.enable_compare = enable; - self - } -} - -/// Manages the raw `SDL_GPUSampler` pointer and releases it on drop -struct SamplerContainer { - raw: *mut SDL_GPUSampler, - device: WeakDevice, -} -impl Drop for SamplerContainer { - fn drop(&mut self) { - if let Some(device) = self.device.upgrade() { - unsafe { SDL_ReleaseGPUSampler(device.raw(), self.raw) } - } - } -} - -#[derive(Clone)] -pub struct Sampler { - inner: Arc, -} -impl Sampler { - pub(super) fn new(device: &Device, raw_sampler: *mut SDL_GPUSampler) -> Self { - Self { - inner: Arc::new(SamplerContainer { - raw: raw_sampler, - device: device.weak(), - }), - } - } - - #[inline] - fn raw(&self) -> *mut SDL_GPUSampler { - self.inner.raw - } -} - -#[repr(C)] -#[derive(Default)] -pub struct TextureSamplerBinding { - inner: SDL_GPUTextureSamplerBinding, -} -impl TextureSamplerBinding { - pub fn new() -> Self { - Default::default() - } - - /// The texture to bind. Must have been created with [`SDL_GPU_TEXTUREUSAGE_SAMPLER`]. - pub fn with_texture(mut self, texture: &Texture<'static>) -> Self { - self.inner.texture = texture.raw(); - self - } - - /// The sampler to bind. - pub fn with_sampler(mut self, sampler: &Sampler) -> Self { - self.inner.sampler = sampler.raw(); - self - } -} - -/// Manages the raw `SDL_GPUTexture` pointer and releases it on drop (if necessary) -enum TextureContainer { - /// The user is responsible for releasing this texture - UserManaged { - raw: *mut SDL_GPUTexture, - device: WeakDevice, - }, - /// SDL owns this texture and is responsible for releasing it - SdlManaged { raw: *mut SDL_GPUTexture }, -} -impl TextureContainer { - fn raw(&self) -> *mut SDL_GPUTexture { - match self { - Self::UserManaged { raw, .. } => *raw, - Self::SdlManaged { raw } => *raw, - } - } -} -impl Drop for TextureContainer { - #[doc(alias = "SDL_ReleaseGPUTexture")] - fn drop(&mut self) { - match self { - Self::UserManaged { raw, device } => { - if let Some(device) = device.upgrade() { - unsafe { SDL_ReleaseGPUTexture(device.raw(), *raw) }; - } - } - _ => {} - } - } -} - -// Texture has a lifetime for the case of the special swapchain texture that must not -// live longer than the command buffer it is bound to. Otherwise, it is always 'static. -#[derive(Clone)] -pub struct Texture<'a> { - inner: Arc, - width: u32, - height: u32, - _phantom: PhantomData<&'a ()>, -} -impl<'a> Texture<'a> { - pub(super) fn new( - device: &Device, - raw: *mut SDL_GPUTexture, - width: u32, - height: u32, - ) -> Texture<'a> { - Texture { - inner: Arc::new(TextureContainer::UserManaged { - raw, - device: device.weak(), - }), - width, - height, - _phantom: Default::default(), - } - } - - pub(super) fn new_sdl_managed( - raw: *mut SDL_GPUTexture, - width: u32, - height: u32, - ) -> Texture<'a> { - Texture { - inner: Arc::new(TextureContainer::SdlManaged { raw }), - width, - height, - _phantom: Default::default(), - } - } - - #[inline] - pub fn raw(&self) -> *mut SDL_GPUTexture { - self.inner.raw() - } - - pub fn width(&self) -> u32 { - self.width - } - - pub fn height(&self) -> u32 { - self.height - } -} - -#[derive(Default)] -pub struct TextureCreateInfo { - pub(super) inner: SDL_GPUTextureCreateInfo, -} -impl TextureCreateInfo { - pub fn new() -> Self { - Default::default() - } - - /// The base dimensionality of the texture. - pub fn with_type(mut self, value: TextureType) -> Self { - self.inner.r#type = SDL_GPUTextureType(value as i32); - self - } - - /// The pixel format of the texture. - pub fn with_format(mut self, format: TextureFormat) -> Self { - self.inner.format = SDL_GPUTextureFormat(format as i32); - self - } - - /// How the texture is intended to be used by the client. - pub fn with_usage(mut self, value: TextureUsage) -> Self { - self.inner.usage = value as u32; - self - } - - /// The width of the texture. - pub fn with_width(mut self, value: u32) -> Self { - self.inner.width = value; - self - } - - /// The height of the texture. - pub fn with_height(mut self, value: u32) -> Self { - self.inner.height = value; - self - } - - /// The layer count or depth of the texture. This value is treated as a layer count on 2D array textures, and as a depth value on 3D textures. - pub fn with_layer_count_or_depth(mut self, value: u32) -> Self { - self.inner.layer_count_or_depth = value; - self - } - - /// The number of mip levels in the texture. - pub fn with_num_levels(mut self, value: u32) -> Self { - self.inner.num_levels = value; - self - } - - /// The number of samples per texel. Only applies if the texture is used as a render target. - pub fn with_sample_count(mut self, value: SampleCount) -> Self { - self.inner.sample_count = SDL_GPUSampleCount(value as i32); - self - } -} diff --git a/src/sdl3/gpu/util.rs b/src/sdl3/gpu/util.rs new file mode 100644 index 00000000..f3b818c9 --- /dev/null +++ b/src/sdl3/gpu/util.rs @@ -0,0 +1,28 @@ +use std::{mem::ManuallyDrop, ptr::NonNull}; + +use crate::{get_error, Error}; + +use super::Extern; + +#[inline(always)] +pub(super) fn nonnull_ext_or_get_error(ptr: *mut T) -> Result>, Error> { + NonNull::new(ptr.cast()).ok_or_else(|| get_error()) +} + +/// Calls the closure when being dropped +pub(super) struct Defer(ManuallyDrop); +impl Defer { + pub(super) fn new(f: F) -> Self { + Self(ManuallyDrop::new(f)) + } +} + +impl Drop for Defer { + fn drop(&mut self) { + // Safety: This is the last (and only) use of the value + unsafe { + let f = ManuallyDrop::take(&mut self.0); + f(); + } + } +}