mirror of
https://github.com/ScrelliCopter/NeHe-SDL_GPU.git
synced 2025-06-19 21:49:17 +10:00
rust: Implement lesson10
This commit is contained in:
@@ -44,3 +44,7 @@ path = "src/rust/lesson8.rs"
|
||||
[[bin]]
|
||||
name = "lesson9"
|
||||
path = "src/rust/lesson9.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "lesson10"
|
||||
path = "src/rust/lesson10.rs"
|
||||
|
||||
2
build.rs
2
build.rs
@@ -44,6 +44,8 @@ pub fn main()
|
||||
"Crate.bmp",
|
||||
"Glass.bmp",
|
||||
"Star.bmp",
|
||||
"Mud.bmp",
|
||||
"World.txt",
|
||||
]);
|
||||
copy_resources(&src_dir.join("shaders"), &dst_dir.join("Shaders"),
|
||||
&[
|
||||
|
||||
369
src/rust/lesson10.rs
Normal file
369
src/rust/lesson10.rs
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* 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::everything::SDL_Keycode;
|
||||
use sdl3_sys::gpu::*;
|
||||
use sdl3_sys::keyboard::SDL_GetKeyboardState;
|
||||
use sdl3_sys::keycode::{SDLK_B, SDLK_F};
|
||||
use sdl3_sys::pixels::SDL_FColor;
|
||||
use sdl3_sys::scancode::*;
|
||||
use std::cmp::max;
|
||||
use std::ffi::c_void;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader};
|
||||
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,
|
||||
u: f32, v: f32,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Sector { pub vertices: Vec<Vertex> }
|
||||
|
||||
impl Sector
|
||||
{
|
||||
fn load_world(&mut self, filename: &str) -> Result<(), NeHeError>
|
||||
{
|
||||
// Read world file as lines
|
||||
let file = File::open(filename)?;
|
||||
let mut lines = BufReader::new(file).lines()
|
||||
.map(|line| line.unwrap())
|
||||
.filter(|line| !line.trim().is_empty() && !line.starts_with("/"));
|
||||
|
||||
// Read the number of triangles
|
||||
let first = lines.next()
|
||||
.ok_or(NeHeError::Fatal("Empty world text file"))?;
|
||||
let num_triangles = first.strip_prefix("NUMPOLLIES ")
|
||||
.ok_or(NeHeError::Fatal("World file didn't start with NUMPOLLIES"))?
|
||||
.parse::<usize>()
|
||||
.map_err(|_| NeHeError::Fatal("Invalid NUMPOLLIES definition"))?;
|
||||
|
||||
// Read remaining lines of "%f %f %f %f %f" as triangle list vertices
|
||||
self.vertices = lines.take(3 * num_triangles).map(|line|
|
||||
{
|
||||
let mut tokens = line.split_whitespace();
|
||||
let mut scanf = || tokens.next()
|
||||
.map_or(0.0, |token| token.parse::<f32>()
|
||||
.unwrap_or(0.0));
|
||||
Vertex
|
||||
{
|
||||
x: scanf(), y: scanf(), z: scanf(),
|
||||
u: scanf(), v: scanf(),
|
||||
}
|
||||
}).collect();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Camera
|
||||
{
|
||||
x: f32, z: f32,
|
||||
yaw: f32, pitch: f32,
|
||||
walk_bob: f32, walk_bob_theta: f32,
|
||||
}
|
||||
|
||||
impl Camera
|
||||
{
|
||||
fn update(&mut self, keys: &[bool])
|
||||
{
|
||||
if keys[SDL_SCANCODE_UP.0 as usize]
|
||||
{
|
||||
self.x -= (self.yaw * Self::PI_OVER_180).sin() * 0.05;
|
||||
self.z -= (self.yaw * Self::PI_OVER_180).cos() * 0.05;
|
||||
self.update_walk_bob(10.0)
|
||||
}
|
||||
if keys[SDL_SCANCODE_DOWN.0 as usize]
|
||||
{
|
||||
self.x += (self.yaw * Self::PI_OVER_180).sin() * 0.05;
|
||||
self.z += (self.yaw * Self::PI_OVER_180).cos() * 0.05;
|
||||
self.update_walk_bob(-10.0)
|
||||
}
|
||||
if keys[SDL_SCANCODE_LEFT.0 as usize] { self.yaw += 1.0; }
|
||||
if keys[SDL_SCANCODE_RIGHT.0 as usize] { self.yaw -= 1.0; }
|
||||
if keys[SDL_SCANCODE_PAGEUP.0 as usize] { self.pitch -= 1.0; }
|
||||
if keys[SDL_SCANCODE_PAGEDOWN.0 as usize] { self.pitch += 1.0; }
|
||||
}
|
||||
|
||||
fn position(&self) -> (f32, f32, f32) { (self.x, Self::HEIGHT + self.walk_bob, self.z) }
|
||||
|
||||
fn update_walk_bob(&mut self, delta: f32)
|
||||
{
|
||||
if delta.is_sign_positive() && self.walk_bob_theta >= 359.0
|
||||
{
|
||||
self.walk_bob_theta = 0.0;
|
||||
}
|
||||
else if delta.is_sign_negative() && self.walk_bob_theta <= 1.0
|
||||
{
|
||||
self.walk_bob_theta = 359.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.walk_bob_theta += delta;
|
||||
}
|
||||
self.walk_bob = (self.walk_bob_theta * Self::PI_OVER_180).sin() / 20.0;
|
||||
}
|
||||
|
||||
const HEIGHT: f32 = 0.25;
|
||||
const PI_OVER_180: f32 = 0.0174532925;
|
||||
}
|
||||
|
||||
struct Lesson10
|
||||
{
|
||||
pso: *mut SDL_GPUGraphicsPipeline,
|
||||
pso_blend: *mut SDL_GPUGraphicsPipeline,
|
||||
vtx_buffer: *mut SDL_GPUBuffer,
|
||||
samplers: [*mut SDL_GPUSampler; 3],
|
||||
texture: *mut SDL_GPUTexture,
|
||||
|
||||
projection: Mtx,
|
||||
|
||||
blending: bool,
|
||||
filter: usize,
|
||||
|
||||
camera: Camera,
|
||||
world: Sector,
|
||||
}
|
||||
|
||||
//FIXME: remove when `raw_ptr_default`
|
||||
impl Default for Lesson10
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Lesson10
|
||||
{
|
||||
pso: null_mut(),
|
||||
pso_blend: null_mut(),
|
||||
vtx_buffer: null_mut(),
|
||||
samplers: [null_mut(); 3],
|
||||
texture: null_mut(),
|
||||
|
||||
projection: Mtx::IDENTITY,
|
||||
|
||||
blending: false,
|
||||
filter: 0,
|
||||
|
||||
camera: Camera::default(),
|
||||
world: Sector::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppImplementation for Lesson10
|
||||
{
|
||||
const TITLE: &'static str = "Lionel Brits & NeHe's 3D World 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>
|
||||
{
|
||||
self.world.load_world("Data/World.txt")?;
|
||||
|
||||
let (vertex_shader, fragment_shader) = ctx.load_shaders("lesson6", 1, 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: 1,
|
||||
buffer_slot: 0,
|
||||
format: SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2,
|
||||
offset: offset_of!(Vertex, u) as u32,
|
||||
},
|
||||
];
|
||||
|
||||
let mut pso_info = SDL_GPUGraphicsPipelineCreateInfo::default();
|
||||
pso_info.vertex_shader = vertex_shader;
|
||||
pso_info.fragment_shader = fragment_shader;
|
||||
pso_info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
|
||||
pso_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,
|
||||
};
|
||||
|
||||
pso_info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
||||
pso_info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
|
||||
pso_info.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||
|
||||
// Common pipeline target info setup
|
||||
let mut color_targets = [ SDL_GPUColorTargetDescription::default() ];
|
||||
color_targets[0].format = unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) };
|
||||
pso_info.target_info.color_target_descriptions = color_targets.as_ptr();
|
||||
pso_info.target_info.num_color_targets = color_targets.len() as u32;
|
||||
pso_info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||
pso_info.target_info.has_depth_stencil_target = true;
|
||||
|
||||
// Create blend pipeline (no depth test)
|
||||
color_targets[0].blend_state.enable_blend = true;
|
||||
color_targets[0].blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_targets[0].blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_targets[0].blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
color_targets[0].blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
color_targets[0].blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
color_targets[0].blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
self.pso_blend = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &pso_info).as_mut() }
|
||||
.ok_or(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"))?;
|
||||
|
||||
// Create regular pipeline w/ depth testing
|
||||
pso_info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS;
|
||||
pso_info.depth_stencil_state.enable_depth_test = true;
|
||||
pso_info.depth_stencil_state.enable_depth_write = true;
|
||||
color_targets[0].blend_state = SDL_GPUColorTargetBlendState::default();
|
||||
self.pso = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &pso_info).as_mut() }
|
||||
.ok_or(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"))?;
|
||||
|
||||
// Create texture samplers (nearest, linear, and trilinear mipmap)
|
||||
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;
|
||||
if enable_mip
|
||||
{
|
||||
sampler_info.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
|
||||
sampler_info.max_lod = f32::MAX;
|
||||
}
|
||||
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)?;
|
||||
|
||||
// Upload texture and world vertex data
|
||||
ctx.copy_pass(|pass|
|
||||
{
|
||||
self.texture = pass.load_texture("Data/Mud.bmp", true, true)?;
|
||||
self.vtx_buffer = pass.create_buffer(SDL_GPU_BUFFERUSAGE_VERTEX, &*self.world.vertices)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn quit(&mut self, ctx: &NeHeContext)
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
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);
|
||||
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso_blend);
|
||||
}
|
||||
}
|
||||
|
||||
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.0 };
|
||||
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, if self.blending { self.pso_blend } else { self.pso });
|
||||
|
||||
// Bind texture
|
||||
let texture_binding = SDL_GPUTextureSamplerBinding { texture: self.texture, sampler: self.samplers[self.filter] };
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &texture_binding, 1);
|
||||
|
||||
// Bind world mesh vertex buffer
|
||||
let vtx_binding = SDL_GPUBufferBinding { buffer: self.vtx_buffer, offset: 0 };
|
||||
SDL_BindGPUVertexBuffers(pass, 0, &vtx_binding, 1);
|
||||
|
||||
// Setup the camera view matrix
|
||||
let mut model_view = Mtx::rotation(self.camera.pitch, 1.0, 0.0, 0.0);
|
||||
model_view.rotate(360.0 - self.camera.yaw, 0.0, 1.0, 0.0);
|
||||
let (cam_x, cam_y, cam_z) = self.camera.position();
|
||||
model_view.translate(-cam_x, -cam_y, -cam_z);
|
||||
|
||||
// Push shader uniforms
|
||||
#[allow(dead_code)]
|
||||
struct Uniforms { model_view_proj: Mtx, color: [f32; 4] }
|
||||
let u = Uniforms { model_view_proj: self.projection * model_view, color: [1.0; 4] };
|
||||
SDL_PushGPUVertexUniformData(cmd, 0, addr_of!(u) as *const c_void, size_of::<Uniforms>() as u32);
|
||||
|
||||
// Draw world
|
||||
SDL_DrawGPUPrimitives(pass, self.world.vertices.len() as u32, 1, 0, 0);
|
||||
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
// Handle keyboard input
|
||||
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)
|
||||
};
|
||||
self.camera.update(keys);
|
||||
}
|
||||
|
||||
fn key(&mut self, _ctx: &NeHeContext, key: SDL_Keycode, down: bool, repeat: bool)
|
||||
{
|
||||
match key
|
||||
{
|
||||
SDLK_B if down && !repeat => 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::<Lesson10>()?;
|
||||
Ok(ExitCode::SUCCESS)
|
||||
}
|
||||
@@ -65,7 +65,7 @@ impl Mtx
|
||||
Self::make_rotation(theta.cos(), theta.sin(), nx, ny, nz)
|
||||
}
|
||||
|
||||
pub(crate) fn rotation(angle: f32, x: f32, y: f32, z: f32) -> Self
|
||||
pub fn rotation(angle: f32, x: f32, y: f32, z: f32) -> Self
|
||||
{
|
||||
let r = Self::make_gl_rotation(angle, x, y, z);
|
||||
Self::new(
|
||||
|
||||
Reference in New Issue
Block a user