rust: Implement lesson08

This commit is contained in:
2025-06-13 14:30:29 +10:00
parent 1b4a78f5cb
commit 79ed26d26d
4 changed files with 415 additions and 2 deletions

View File

@@ -36,3 +36,7 @@ path = "src/rust/lesson6.rs"
[[bin]]
name = "lesson7"
path = "src/rust/lesson7.rs"
[[bin]]
name = "lesson8"
path = "src/rust/lesson8.rs"

View File

@@ -42,6 +42,7 @@ pub fn main()
&[
"NeHe.bmp",
"Crate.bmp",
"Glass.bmp",
]);
copy_resources(&src_dir.join("shaders"), &dst_dir.join("Shaders"),
&[

View File

@@ -239,9 +239,10 @@ impl AppImplementation for Lesson7
}
// Create texture samplers
let create_sampler = |filter: SDL_GPUFilter, enable_mip: bool| -> Result<&mut SDL_GPUSampler, NeHeError>
let create_sampler = |filter: SDL_GPUFilter, enable_mip: bool|
-> Result<&mut SDL_GPUSampler, NeHeError>
{
let mut sampler_info: SDL_GPUSamplerCreateInfo = unsafe { std::mem::zeroed() };
let mut sampler_info = SDL_GPUSamplerCreateInfo::default();
sampler_info.min_filter = filter;
sampler_info.mag_filter = filter;
sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;

407
src/rust/lesson8.rs Normal file
View File

@@ -0,0 +1,407 @@
/*
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
* SPDX-License-Identifier: Zlib
*/
use nehe::application::config::AppImplementation;
use nehe::application::run;
use nehe::context::NeHeContext;
use nehe::error::NeHeError;
use nehe::matrix::Mtx;
use sdl3_sys::gpu::*;
use sdl3_sys::keyboard::SDL_GetKeyboardState;
use sdl3_sys::keycode::{SDL_Keycode, SDLK_B, SDLK_F, SDLK_L};
use sdl3_sys::pixels::SDL_FColor;
use sdl3_sys::scancode::{SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_PAGEDOWN, SDL_SCANCODE_PAGEUP, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP};
use std::cmp::max;
use std::ffi::c_void;
use std::mem::offset_of;
use std::process::ExitCode;
use std::ptr::{addr_of, null_mut};
#[repr(C)]
struct Vertex
{
x: f32, y: f32, z: f32,
nx: f32, ny: f32, nz: f32,
u: f32, v: f32,
}
const VERTICES: &'static [Vertex] =
&[
// Front Face
Vertex { x: -1.0, y: -1.0, z: 1.0, nx: 0.0, ny: 0.0, nz: 1.0, u: 0.0, v: 0.0 },
Vertex { x: 1.0, y: -1.0, z: 1.0, nx: 0.0, ny: 0.0, nz: 1.0, u: 1.0, v: 0.0 },
Vertex { x: 1.0, y: 1.0, z: 1.0, nx: 0.0, ny: 0.0, nz: 1.0, u: 1.0, v: 1.0 },
Vertex { x: -1.0, y: 1.0, z: 1.0, nx: 0.0, ny: 0.0, nz: 1.0, u: 0.0, v: 1.0 },
// Back Face
Vertex { x: -1.0, y: -1.0, z: -1.0, nx: 0.0, ny: 0.0, nz: -1.0, u: 1.0, v: 0.0 },
Vertex { x: -1.0, y: 1.0, z: -1.0, nx: 0.0, ny: 0.0, nz: -1.0, u: 1.0, v: 1.0 },
Vertex { x: 1.0, y: 1.0, z: -1.0, nx: 0.0, ny: 0.0, nz: -1.0, u: 0.0, v: 1.0 },
Vertex { x: 1.0, y: -1.0, z: -1.0, nx: 0.0, ny: 0.0, nz: -1.0, u: 0.0, v: 0.0 },
// Top Face
Vertex { x: -1.0, y: 1.0, z: -1.0, nx: 0.0, ny: 1.0, nz: 0.0, u: 0.0, v: 1.0 },
Vertex { x: -1.0, y: 1.0, z: 1.0, nx: 0.0, ny: 1.0, nz: 0.0, u: 0.0, v: 0.0 },
Vertex { x: 1.0, y: 1.0, z: 1.0, nx: 0.0, ny: 1.0, nz: 0.0, u: 1.0, v: 0.0 },
Vertex { x: 1.0, y: 1.0, z: -1.0, nx: 0.0, ny: 1.0, nz: 0.0, u: 1.0, v: 1.0 },
// Bottom Face
Vertex { x: -1.0, y: -1.0, z: -1.0, nx: 0.0, ny: -1.0, nz: 0.0, u: 1.0, v: 1.0 },
Vertex { x: 1.0, y: -1.0, z: -1.0, nx: 0.0, ny: -1.0, nz: 0.0, u: 0.0, v: 1.0 },
Vertex { x: 1.0, y: -1.0, z: 1.0, nx: 0.0, ny: -1.0, nz: 0.0, u: 0.0, v: 0.0 },
Vertex { x: -1.0, y: -1.0, z: 1.0, nx: 0.0, ny: -1.0, nz: 0.0, u: 1.0, v: 0.0 },
// Right face
Vertex { x: 1.0, y: -1.0, z: -1.0, nx: 1.0, ny: 0.0, nz: 0.0, u: 1.0, v: 0.0 },
Vertex { x: 1.0, y: 1.0, z: -1.0, nx: 1.0, ny: 0.0, nz: 0.0, u: 1.0, v: 1.0 },
Vertex { x: 1.0, y: 1.0, z: 1.0, nx: 1.0, ny: 0.0, nz: 0.0, u: 0.0, v: 1.0 },
Vertex { x: 1.0, y: -1.0, z: 1.0, nx: 1.0, ny: 0.0, nz: 0.0, u: 0.0, v: 0.0 },
// Left Face
Vertex { x: -1.0, y: -1.0, z: -1.0, nx: -1.0, ny: 0.0, nz: 0.0, u: 0.0, v: 0.0 },
Vertex { x: -1.0, y: -1.0, z: 1.0, nx: -1.0, ny: 0.0, nz: 0.0, u: 1.0, v: 0.0 },
Vertex { x: -1.0, y: 1.0, z: 1.0, nx: -1.0, ny: 0.0, nz: 0.0, u: 1.0, v: 1.0 },
Vertex { x: -1.0, y: 1.0, z: -1.0, nx: -1.0, ny: 0.0, nz: 0.0, u: 0.0, v: 1.0 },
];
const INDICES: &'static [u16] =
&[
0, 1, 2, 2, 3, 0, // Front
4, 5, 6, 6, 7, 4, // Back
8, 9, 10, 10, 11, 8, // Top
12, 13, 14, 14, 15, 12, // Bottom
16, 17, 18, 18, 19, 16, // Right
20, 21, 22, 22, 23, 20 // Left
];
#[allow(dead_code)]
struct Light
{
ambient: (f32, f32, f32, f32),
diffuse: (f32, f32, f32, f32),
position: (f32, f32, f32, f32),
}
struct Lesson8
{
pso_unlit: *mut SDL_GPUGraphicsPipeline,
pso_light: *mut SDL_GPUGraphicsPipeline,
pso_blend_unlit: *mut SDL_GPUGraphicsPipeline,
pso_blend_light: *mut SDL_GPUGraphicsPipeline,
vtx_buffer: *mut SDL_GPUBuffer,
idx_buffer: *mut SDL_GPUBuffer,
samplers: [*mut SDL_GPUSampler; 3],
texture: *mut SDL_GPUTexture,
projection: Mtx,
lighting: bool,
blending: bool,
light: Light,
filter: usize,
rot: (f32, f32),
speed: (f32, f32),
z: f32,
}
impl Default for Lesson8
{
fn default() -> Self
{
Self
{
pso_unlit: null_mut(),
pso_light: null_mut(),
pso_blend_unlit: null_mut(),
pso_blend_light: null_mut(),
vtx_buffer: null_mut(),
idx_buffer: null_mut(),
samplers: [null_mut(); 3],
texture: null_mut(),
projection: Mtx::IDENTITY,
lighting: false,
blending: false,
light: Light
{
ambient: (0.5, 0.5, 0.5, 1.0),
diffuse: (1.0, 1.0, 1.0, 1.0),
position: (0.0, 0.0, 2.0, 1.0),
},
filter: 0,
rot: (0.0, 0.0),
speed: (0.0, 0.0),
z: -5.0,
}
}
}
impl AppImplementation for Lesson8
{
const TITLE: &'static str = "Tom Stanis & NeHe's Blending Tutorial";
const WIDTH: i32 = 640;
const HEIGHT: i32 = 480;
const CREATE_DEPTH_BUFFER: SDL_GPUTextureFormat = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
fn init(&mut self, ctx: &NeHeContext) -> Result<(), NeHeError>
{
let (vertex_shader_unlit, fragment_shader_unlit) = ctx.load_shaders("lesson6", 1, 0, 1)?;
let (vertex_shader_light, fragment_shader_light) = ctx.load_shaders("lesson7", 2, 0, 1)?;
const VERTEX_DESCRIPTIONS: &'static [SDL_GPUVertexBufferDescription] =
&[
SDL_GPUVertexBufferDescription
{
slot: 0,
pitch: size_of::<Vertex>() as u32,
input_rate: SDL_GPU_VERTEXINPUTRATE_VERTEX,
instance_step_rate: 0,
},
];
const VERTEX_ATTRIBS: &'static [SDL_GPUVertexAttribute] =
&[
SDL_GPUVertexAttribute
{
location: 0,
buffer_slot: 0,
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
offset: offset_of!(Vertex, x) as u32,
},
SDL_GPUVertexAttribute
{
location: 2,
buffer_slot: 0,
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3,
offset: offset_of!(Vertex, nx) as u32,
},
SDL_GPUVertexAttribute
{
location: 1,
buffer_slot: 0,
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
offset: offset_of!(Vertex, u) as u32,
},
];
let mut info = SDL_GPUGraphicsPipelineCreateInfo::default();
info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
info.vertex_input_state = SDL_GPUVertexInputState
{
vertex_buffer_descriptions: VERTEX_DESCRIPTIONS.as_ptr(),
num_vertex_buffers: VERTEX_DESCRIPTIONS.len() as u32,
vertex_attributes: VERTEX_ATTRIBS.as_ptr(),
num_vertex_attributes: VERTEX_ATTRIBS.len() as u32,
};
info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
// Common pipeline depth & colour target options
let mut color_target = [ SDL_GPUColorTargetDescription::default() ];
color_target[0].format = unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) };
info.target_info.color_target_descriptions = color_target.as_ptr();
info.target_info.num_color_targets = 1;
info.target_info.depth_stencil_format = Self::CREATE_DEPTH_BUFFER;
info.target_info.has_depth_stencil_target = true;
info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
// Setup depth/stencil & colour pipeline state for no blending
info.depth_stencil_state.enable_depth_test = true;
info.depth_stencil_state.enable_depth_write = true;
color_target[0].blend_state = unsafe { std::mem::zeroed() };
// Create unlit pipeline
info.vertex_shader = vertex_shader_unlit;
info.fragment_shader = fragment_shader_unlit;
self.pso_unlit = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info).as_mut() }
.ok_or(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"))?;
// Create lit pipeline
info.vertex_shader = vertex_shader_light;
info.fragment_shader = fragment_shader_light;
self.pso_light = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info).as_mut() }
.ok_or(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"))?;
// Setup depth/stencil & colour pipeline state for blending
info.depth_stencil_state.enable_depth_test = false;
info.depth_stencil_state.enable_depth_write = false;
color_target[0].blend_state.enable_blend = true;
color_target[0].blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
color_target[0].blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
color_target[0].blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
color_target[0].blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
color_target[0].blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
color_target[0].blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
// Create unlit blended pipeline
info.vertex_shader = vertex_shader_unlit;
info.fragment_shader = fragment_shader_unlit;
self.pso_blend_unlit = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info).as_mut() }
.ok_or(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"))?;
// Create lit blended pipeline
info.vertex_shader = vertex_shader_light;
info.fragment_shader = fragment_shader_light;
self.pso_blend_light = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info).as_mut() }
.ok_or(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"))?;
// Free shaders
unsafe
{
SDL_ReleaseGPUShader(ctx.device, fragment_shader_light);
SDL_ReleaseGPUShader(ctx.device, vertex_shader_light);
SDL_ReleaseGPUShader(ctx.device, fragment_shader_unlit);
SDL_ReleaseGPUShader(ctx.device, vertex_shader_unlit);
}
// Create texture samplers
let create_sampler = |filter: SDL_GPUFilter, enable_mip: bool|
-> Result<&mut SDL_GPUSampler, NeHeError>
{
let mut sampler_info = SDL_GPUSamplerCreateInfo::default();
sampler_info.min_filter = filter;
sampler_info.mag_filter = filter;
sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
sampler_info.max_lod = if enable_mip { f32::MAX } else { 0.0 };
unsafe { SDL_CreateGPUSampler(ctx.device, &sampler_info).as_mut() }
.ok_or(NeHeError::sdl("SDL_CreateGPUSampler"))
};
self.samplers[0] = create_sampler(SDL_GPU_FILTER_NEAREST, false)?;
self.samplers[1] = create_sampler(SDL_GPU_FILTER_LINEAR, false)?;
self.samplers[2] = create_sampler(SDL_GPU_FILTER_LINEAR, true)?;
ctx.copy_pass(|pass|
{
self.texture = pass.load_texture("Data/Glass.bmp", true, true)?;
self.vtx_buffer = pass.create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, VERTICES)?;
self.idx_buffer = pass.create_buffer(SDL_GPU_BUFFERUSAGE_INDEX, INDICES)?;
Ok(())
})
}
fn quit(&mut self, ctx: &NeHeContext)
{
unsafe
{
SDL_ReleaseGPUBuffer(ctx.device, self.idx_buffer);
SDL_ReleaseGPUBuffer(ctx.device, self.vtx_buffer);
SDL_ReleaseGPUTexture(ctx.device, self.texture);
self.samplers.iter().rev().for_each(|sampler| SDL_ReleaseGPUSampler(ctx.device, *sampler));
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso_blend_light);
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso_blend_unlit);
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso_light);
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso_unlit);
}
}
fn resize(&mut self, _ctx: &NeHeContext, width: i32, height: i32)
{
let aspect = width as f32 / max(height, 1) as f32;
self.projection = Mtx::perspective(45.0, aspect, 0.1, 100.0);
}
fn draw(&mut self, ctx: &NeHeContext, cmd: *mut SDL_GPUCommandBuffer, swapchain: *mut SDL_GPUTexture)
{
let mut color_info = SDL_GPUColorTargetInfo::default();
color_info.texture = swapchain;
color_info.clear_color = SDL_FColor { r: 0.0, g: 0.0, b: 0.0, a: 0.5 };
color_info.load_op = SDL_GPU_LOADOP_CLEAR;
color_info.store_op = SDL_GPU_STOREOP_STORE;
let mut depth_info = SDL_GPUDepthStencilTargetInfo::default();
depth_info.texture = ctx.depth_texture;
depth_info.clear_depth = 1.0;
depth_info.load_op = SDL_GPU_LOADOP_CLEAR;
depth_info.store_op = SDL_GPU_STOREOP_DONT_CARE;
depth_info.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
depth_info.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
depth_info.cycle = true;
unsafe
{
// Begin pass & bind pipeline state
let pass = SDL_BeginGPURenderPass(cmd, &color_info, 1, &depth_info);
SDL_BindGPUGraphicsPipeline(pass, match (self.blending, self.lighting)
{
(false, false) => self.pso_unlit,
(false, true) => self.pso_light,
(true, false) => self.pso_blend_unlit,
(true, true) => self.pso_blend_light,
});
// Bind texture
let texture_binding = SDL_GPUTextureSamplerBinding
{
texture: self.texture,
sampler: self.samplers[self.filter],
};
SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
// Bind vertex & index buffers
let vtx_binding = SDL_GPUBufferBinding { buffer: self.vtx_buffer, offset: 0 };
let idx_binding = SDL_GPUBufferBinding { buffer: self.idx_buffer, offset: 0 };
SDL_BindGPUVertexBuffers(pass, 0, &vtx_binding, 1);
SDL_BindGPUIndexBuffer(pass, &idx_binding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
// Setup the cube's model matrix
let mut model = Mtx::translation(0.0, 0.0, self.z);
model.rotate(self.rot.0, 1.0, 0.0, 0.0);
model.rotate(self.rot.1, 0.0, 1.0, 0.0);
// Push shader uniforms
if self.lighting
{
#[allow(dead_code)]
struct Uniforms { model: Mtx, projection: Mtx }
let u = Uniforms { model, projection: self.projection };
SDL_PushGPUVertexUniformData(cmd, 0, addr_of!(u) as *const c_void, size_of::<Uniforms>() as u32);
SDL_PushGPUVertexUniformData(cmd, 1, addr_of!(self.light) as *const c_void, size_of::<Light>() as u32);
}
else
{
#[allow(dead_code)]
struct Uniforms { model_view_proj: Mtx, color: [f32; 4] }
let color = [1.0, 1.0, 1.0, 0.5]; // 50% translucency
let u = Uniforms { model_view_proj: self.projection * model, color };
SDL_PushGPUVertexUniformData(cmd, 0, addr_of!(u) as *const c_void, size_of::<Uniforms>() as u32);
}
// Draw the textured cube
SDL_DrawGPUIndexedPrimitives(pass, INDICES.len() as u32, 1, 0, 0, 0);
SDL_EndGPURenderPass(pass);
}
let keys = unsafe
{
let mut numkeys: std::ffi::c_int = 0;
let keys = SDL_GetKeyboardState(&mut numkeys);
std::slice::from_raw_parts(keys, numkeys as usize)
};
if keys[SDL_SCANCODE_PAGEUP.0 as usize] { self.z -= 0.02 }
if keys[SDL_SCANCODE_PAGEDOWN.0 as usize] { self.z += 0.02; }
if keys[SDL_SCANCODE_UP.0 as usize] { self.speed.0 -= 0.01; }
if keys[SDL_SCANCODE_DOWN.0 as usize] { self.speed.0 += 0.01; }
if keys[SDL_SCANCODE_RIGHT.0 as usize] { self.speed.1 += 0.1; }
if keys[SDL_SCANCODE_LEFT.0 as usize] { self.speed.1 -= 0.1; }
self.rot.0 += self.speed.0;
self.rot.1 += self.speed.1;
}
fn key(&mut self, _ctx: &NeHeContext, key: SDL_Keycode, down: bool, _repeat: bool)
{
match key
{
SDLK_L if down => self.lighting = !self.lighting,
SDLK_B if down => self.blending = !self.blending,
SDLK_F if down => self.filter = (self.filter + 1) % self.samplers.len(),
_ => ()
}
}
}
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
{
run::<Lesson8>()?;
Ok(ExitCode::SUCCESS)
}