mirror of
https://github.com/ScrelliCopter/NeHe-SDL_GPU.git
synced 2025-06-19 21:49:17 +10:00
rust: Implement lessons 1-7
This commit is contained in:
38
Cargo.toml
Normal file
38
Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "NeHe-SDL_GPU"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sdl3-sys = { version = "0.5.1", features = [ "link-framework" ] }
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "nehe"
|
||||||
|
path = "src/rust/nehe/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson1"
|
||||||
|
path = "src/rust/lesson1.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson2"
|
||||||
|
path = "src/rust/lesson2.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson3"
|
||||||
|
path = "src/rust/lesson3.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson4"
|
||||||
|
path = "src/rust/lesson4.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson5"
|
||||||
|
path = "src/rust/lesson5.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson6"
|
||||||
|
path = "src/rust/lesson6.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "lesson7"
|
||||||
|
path = "src/rust/lesson7.rs"
|
||||||
53
build.rs
Normal file
53
build.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
pub fn get_source_dir() -> PathBuf
|
||||||
|
{
|
||||||
|
std::env::current_dir().unwrap().join("src")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_target_dir() -> PathBuf
|
||||||
|
{
|
||||||
|
//let out_dir = std::env::var("OUT_DIR").unwrap();
|
||||||
|
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
|
||||||
|
let build_type = std::env::var("PROFILE").unwrap();
|
||||||
|
Path::new(&manifest_dir).join("target").join(build_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_resources<const N: usize>(src_dir: &PathBuf, dst_dir: &PathBuf, resources: &[&str; N])
|
||||||
|
{
|
||||||
|
if !dst_dir.is_dir()
|
||||||
|
{
|
||||||
|
std::fs::create_dir(&dst_dir).unwrap();
|
||||||
|
}
|
||||||
|
for resource in resources
|
||||||
|
{
|
||||||
|
std::fs::copy(&src_dir.join(resource), &dst_dir.join(resource)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main()
|
||||||
|
{
|
||||||
|
#[cfg(target_os="macos")]
|
||||||
|
println!("cargo:rustc-link-arg=-Wl,-rpath,/Library/Frameworks");
|
||||||
|
|
||||||
|
let src_dir = std::env::current_dir().unwrap().join("data");
|
||||||
|
let dst_dir = get_target_dir().join("Data");
|
||||||
|
|
||||||
|
copy_resources(&src_dir, &dst_dir,
|
||||||
|
&[
|
||||||
|
"NeHe.bmp",
|
||||||
|
"Crate.bmp",
|
||||||
|
]);
|
||||||
|
copy_resources(&src_dir.join("shaders"), &dst_dir.join("Shaders"),
|
||||||
|
&[
|
||||||
|
"lesson2.metallib",
|
||||||
|
"lesson3.metallib",
|
||||||
|
"lesson6.metallib",
|
||||||
|
"lesson7.metallib",
|
||||||
|
]);
|
||||||
|
}
|
||||||
43
src/rust/lesson1.rs
Normal file
43
src/rust/lesson1.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
use nehe::application::config::AppImplementation;
|
||||||
|
use nehe::application::run;
|
||||||
|
use nehe::context::NeHeContext;
|
||||||
|
use sdl3_sys::gpu::*;
|
||||||
|
use sdl3_sys::pixels::SDL_FColor;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Lesson1;
|
||||||
|
|
||||||
|
impl AppImplementation for Lesson1
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's OpenGL Framework";
|
||||||
|
const WIDTH: i32 = 640;
|
||||||
|
const HEIGHT: i32 = 480;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &color_info, 1, null_mut());
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson1>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
199
src/rust/lesson2.rs
Normal file
199
src/rust/lesson2.rs
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
* 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::pixels::SDL_FColor;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::offset_of;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use std::ptr::{null, null_mut};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex { x: f32, y: f32, z: f32 }
|
||||||
|
|
||||||
|
const VERTICES: &'static [Vertex] =
|
||||||
|
&[
|
||||||
|
// Triangle
|
||||||
|
Vertex { x: 0.0, y: 1.0, z: 0.0 }, // Top
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 0.0 }, // Bottom left
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 0.0 }, // Bottom right
|
||||||
|
// Quad
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 0.0 }, // Top left
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 0.0 }, // Top right
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 0.0 }, // Bottom right
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 0.0 }, // Bottom left
|
||||||
|
];
|
||||||
|
|
||||||
|
const INDICES: &'static [i16] =
|
||||||
|
&[
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3,
|
||||||
|
];
|
||||||
|
|
||||||
|
//#[derive(Default)]
|
||||||
|
struct Lesson2
|
||||||
|
{
|
||||||
|
pso: *mut SDL_GPUGraphicsPipeline,
|
||||||
|
vtx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
idx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
projection: Mtx,
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: remove when `raw_ptr_default`
|
||||||
|
impl Default for Lesson2
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
pso: null_mut(),
|
||||||
|
vtx_buffer: null_mut(),
|
||||||
|
idx_buffer: null_mut(),
|
||||||
|
projection: Mtx::IDENTITY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppImplementation for Lesson2
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's First Polygon Tutorial";
|
||||||
|
const WIDTH: i32 = 640;
|
||||||
|
const HEIGHT: i32 = 480;
|
||||||
|
|
||||||
|
fn init(&mut self, ctx: &NeHeContext) -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
let (vertex_shader, fragment_shader) = ctx.load_shaders("lesson2", 1, 0, 0)?;
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut info = SDL_GPUGraphicsPipelineCreateInfo::default();
|
||||||
|
info.vertex_shader = vertex_shader;
|
||||||
|
info.fragment_shader = fragment_shader;
|
||||||
|
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;
|
||||||
|
let colour_targets: &[SDL_GPUColorTargetDescription] =
|
||||||
|
&[
|
||||||
|
SDL_GPUColorTargetDescription
|
||||||
|
{
|
||||||
|
format: unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) },
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState::default(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
info.target_info.color_target_descriptions = colour_targets.as_ptr();
|
||||||
|
info.target_info.num_color_targets = colour_targets.len() as u32;
|
||||||
|
|
||||||
|
self.pso = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader);
|
||||||
|
}
|
||||||
|
if self.pso.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.copy_pass(|pass|
|
||||||
|
{
|
||||||
|
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_ReleaseGPUGraphicsPipeline(ctx.device, self.pso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &color_info, 1, null());
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
let mut model = Mtx::translation(-1.5, 0.0, -6.0);
|
||||||
|
let mut viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Move to the right by 3 units and draw quad
|
||||||
|
model.translate(3.0, 0.0, 0.0);
|
||||||
|
viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson2>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
209
src/rust/lesson3.rs
Normal file
209
src/rust/lesson3.rs
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
/*
|
||||||
|
* 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::pixels::SDL_FColor;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::offset_of;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use std::ptr::{null, null_mut};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
x: f32, y: f32, z: f32,
|
||||||
|
r: f32, g: f32, b: f32, a: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERTICES: &'static [Vertex] =
|
||||||
|
&[
|
||||||
|
// Triangle
|
||||||
|
Vertex { x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Top (red)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Bottom left (green)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Bottom right (blue)
|
||||||
|
// Quad
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Top left
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Top right
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Bottom right
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Bottom left
|
||||||
|
];
|
||||||
|
|
||||||
|
const INDICES: &'static [i16] =
|
||||||
|
&[
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3,
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Lesson3
|
||||||
|
{
|
||||||
|
pso: *mut SDL_GPUGraphicsPipeline,
|
||||||
|
vtx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
idx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
projection: Mtx,
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: remove when `raw_ptr_default`
|
||||||
|
impl Default for Lesson3
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
pso: null_mut(),
|
||||||
|
vtx_buffer: null_mut(),
|
||||||
|
idx_buffer: null_mut(),
|
||||||
|
projection: Mtx::IDENTITY,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppImplementation for Lesson3
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's Color Tutorial";
|
||||||
|
const WIDTH: i32 = 640;
|
||||||
|
const HEIGHT: i32 = 480;
|
||||||
|
|
||||||
|
fn init(&mut self, ctx: &NeHeContext) -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
let (vertex_shader, fragment_shader) = ctx.load_shaders("lesson3", 1, 0, 0)?;
|
||||||
|
|
||||||
|
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_FLOAT4,
|
||||||
|
offset: offset_of!(Vertex, r) as u32,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut info = SDL_GPUGraphicsPipelineCreateInfo::default();
|
||||||
|
info.vertex_shader = vertex_shader;
|
||||||
|
info.fragment_shader = fragment_shader;
|
||||||
|
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;
|
||||||
|
let colour_targets: &[SDL_GPUColorTargetDescription] =
|
||||||
|
&[
|
||||||
|
SDL_GPUColorTargetDescription
|
||||||
|
{
|
||||||
|
format: unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) },
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState::default(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
info.target_info.color_target_descriptions = colour_targets.as_ptr();
|
||||||
|
info.target_info.num_color_targets = colour_targets.len() as u32;
|
||||||
|
|
||||||
|
self.pso = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader);
|
||||||
|
}
|
||||||
|
if self.pso.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.copy_pass(|pass|
|
||||||
|
{
|
||||||
|
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_ReleaseGPUGraphicsPipeline(ctx.device, self.pso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &color_info, 1, null());
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
let mut model = Mtx::translation(-1.5, 0.0, -6.0);
|
||||||
|
let mut viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Move to the right by 3 units and draw quad
|
||||||
|
model.translate(3.0, 0.0, 0.0);
|
||||||
|
viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson3>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
220
src/rust/lesson4.rs
Normal file
220
src/rust/lesson4.rs
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* 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::pixels::SDL_FColor;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::offset_of;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use std::ptr::{null, null_mut};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
x: f32, y: f32, z: f32,
|
||||||
|
r: f32, g: f32, b: f32, a: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERTICES: &'static [Vertex] =
|
||||||
|
&[
|
||||||
|
// Triangle
|
||||||
|
Vertex { x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Top (red)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 0.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Bottom left (green)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 0.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Bottom right (blue)
|
||||||
|
// Quad
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Top left
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Top right
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Bottom right
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 0.0, r: 0.5, g: 0.5, b: 1.0, a: 1.0 }, // Bottom left
|
||||||
|
];
|
||||||
|
|
||||||
|
const INDICES: &'static [i16] =
|
||||||
|
&[
|
||||||
|
// Triangle
|
||||||
|
0, 1, 2,
|
||||||
|
// Quad
|
||||||
|
3, 4, 5, 5, 6, 3,
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Lesson4
|
||||||
|
{
|
||||||
|
pso: *mut SDL_GPUGraphicsPipeline,
|
||||||
|
vtx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
idx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
projection: Mtx,
|
||||||
|
|
||||||
|
rot_tri: f32,
|
||||||
|
rot_quad: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: remove when `raw_ptr_default`
|
||||||
|
impl Default for Lesson4
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
pso: null_mut(),
|
||||||
|
vtx_buffer: null_mut(),
|
||||||
|
idx_buffer: null_mut(),
|
||||||
|
projection: Mtx::IDENTITY,
|
||||||
|
|
||||||
|
rot_tri: 0.0,
|
||||||
|
rot_quad: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppImplementation for Lesson4
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's Rotation Tutorial";
|
||||||
|
const WIDTH: i32 = 640;
|
||||||
|
const HEIGHT: i32 = 480;
|
||||||
|
|
||||||
|
fn init(&mut self, ctx: &NeHeContext) -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
let (vertex_shader, fragment_shader) = ctx.load_shaders("lesson3", 1, 0, 0)?;
|
||||||
|
|
||||||
|
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_FLOAT4,
|
||||||
|
offset: offset_of!(Vertex, r) as u32,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut info = SDL_GPUGraphicsPipelineCreateInfo::default();
|
||||||
|
info.vertex_shader = vertex_shader;
|
||||||
|
info.fragment_shader = fragment_shader;
|
||||||
|
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;
|
||||||
|
let colour_targets: &[SDL_GPUColorTargetDescription] =
|
||||||
|
&[
|
||||||
|
SDL_GPUColorTargetDescription
|
||||||
|
{
|
||||||
|
format: unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) },
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState::default(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
info.target_info.color_target_descriptions = colour_targets.as_ptr();
|
||||||
|
info.target_info.num_color_targets = colour_targets.len() as u32;
|
||||||
|
|
||||||
|
self.pso = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader);
|
||||||
|
}
|
||||||
|
if self.pso.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.copy_pass(|pass|
|
||||||
|
{
|
||||||
|
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_ReleaseGPUGraphicsPipeline(ctx.device, self.pso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
// Begin pass & bind pipeline state
|
||||||
|
let pass = SDL_BeginGPURenderPass(cmd, &color_info, 1, null());
|
||||||
|
SDL_BindGPUGraphicsPipeline(pass, self.pso);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Draw triangle 1.5 units to the left and 6 units into the camera
|
||||||
|
let mut model = Mtx::translation(-1.5, 0.0, -6.0);
|
||||||
|
model.rotate(self.rot_tri, 0.0, 1.0, 0.0);
|
||||||
|
let mut viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 3, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Draw quad 1.5 units to the right and 6 units into the camera
|
||||||
|
model = Mtx::translation(1.5,0.0,-6.0);
|
||||||
|
model.rotate(self.rot_quad, 1.0, 0.0, 0.0);
|
||||||
|
viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 6, 1, 3, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rot_tri += 0.2;
|
||||||
|
self.rot_quad -= 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson4>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
266
src/rust/lesson5.rs
Normal file
266
src/rust/lesson5.rs
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/*
|
||||||
|
* 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::pixels::SDL_FColor;
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::offset_of;
|
||||||
|
use std::process::ExitCode;
|
||||||
|
use std::ptr::null_mut;
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct Vertex
|
||||||
|
{
|
||||||
|
x: f32, y: f32, z: f32,
|
||||||
|
r: f32, g: f32, b: f32, a: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERTICES: &'static [Vertex] =
|
||||||
|
&[
|
||||||
|
// Pyramid
|
||||||
|
Vertex { x: 0.0, y: 1.0, z: 0.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Top of pyramid (Red)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Front-left of pyramid (Green)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Front-right of pyramid (Blue)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Back-right of pyramid (Green)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Back-left of pyramid (Blue)
|
||||||
|
// Cube
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: -1.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Top-right of top face (Green)
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: -1.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Top-left of top face (Green)
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 1.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Bottom-left of top face (Green)
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 1.0, r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, // Bottom-right of top face (Green)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, r: 1.0, g: 0.5, b: 0.0, a: 1.0 }, // Top-right of bottom face (Orange)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, r: 1.0, g: 0.5, b: 0.0, a: 1.0 }, // Top-left of bottom face (Orange)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, r: 1.0, g: 0.5, b: 0.0, a: 1.0 }, // Bottom-left of bottom face (Orange)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, r: 1.0, g: 0.5, b: 0.0, a: 1.0 }, // Bottom-right of bottom face (Orange)
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 1.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Top-right of front face (Red)
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 1.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Top-left of front face (Red)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Bottom-left of front face (Red)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, r: 1.0, g: 0.0, b: 0.0, a: 1.0 }, // Bottom-right of front face (Red)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, r: 1.0, g: 1.0, b: 0.0, a: 1.0 }, // Top-right of back face (Yellow)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, r: 1.0, g: 1.0, b: 0.0, a: 1.0 }, // Top-left of back face (Yellow)
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: -1.0, r: 1.0, g: 1.0, b: 0.0, a: 1.0 }, // Bottom-left of back face (Yellow)
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: -1.0, r: 1.0, g: 1.0, b: 0.0, a: 1.0 }, // Bottom-right of back face (Yellow)
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 1.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Top-right of left face (Blue)
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: -1.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Top-left of left face (Blue)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Bottom-left of left face (Blue)
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, r: 0.0, g: 0.0, b: 1.0, a: 1.0 }, // Bottom-right of left face (Blue)
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: -1.0, r: 1.0, g: 0.0, b: 1.0, a: 1.0 }, // Top-right of right face (Violet)
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 1.0, r: 1.0, g: 0.0, b: 1.0, a: 1.0 }, // Top-left of right face (Violet)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, r: 1.0, g: 0.0, b: 1.0, a: 1.0 }, // Bottom-left of right face (Violet)
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, r: 1.0, g: 0.0, b: 1.0, a: 1.0 }, // Bottom-right of right face (Violet)
|
||||||
|
];
|
||||||
|
|
||||||
|
const INDICES: &'static [u16] =
|
||||||
|
&[
|
||||||
|
// Pyramid
|
||||||
|
0, 1, 2, // Front
|
||||||
|
0, 2, 3, // Right
|
||||||
|
0, 3, 4, // Back
|
||||||
|
0, 4, 1, // Left
|
||||||
|
// Cube
|
||||||
|
5, 6, 7, 7, 8, 5, // Top
|
||||||
|
9, 10, 11, 11, 12, 9, // Bottom
|
||||||
|
13, 14, 15, 15, 16, 13, // Front
|
||||||
|
17, 18, 19, 19, 20, 17, // Back
|
||||||
|
21, 22, 23, 23, 24, 21, // Left
|
||||||
|
25, 26, 27, 27, 28, 25, // Right
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Lesson5
|
||||||
|
{
|
||||||
|
pso: *mut SDL_GPUGraphicsPipeline,
|
||||||
|
vtx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
idx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
projection: Mtx,
|
||||||
|
|
||||||
|
rot_tri: f32,
|
||||||
|
rot_quad: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: remove when `raw_ptr_default`
|
||||||
|
impl Default for Lesson5
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
pso: null_mut(),
|
||||||
|
vtx_buffer: null_mut(),
|
||||||
|
idx_buffer: null_mut(),
|
||||||
|
projection: Mtx::IDENTITY,
|
||||||
|
|
||||||
|
rot_tri: 0.0,
|
||||||
|
rot_quad: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppImplementation for Lesson5
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's Solid Object 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, fragment_shader) = ctx.load_shaders("lesson3", 1, 0, 0)?;
|
||||||
|
|
||||||
|
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_FLOAT4,
|
||||||
|
offset: offset_of!(Vertex, r) as u32,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut info = SDL_GPUGraphicsPipelineCreateInfo::default();
|
||||||
|
info.vertex_shader = vertex_shader;
|
||||||
|
info.fragment_shader = fragment_shader;
|
||||||
|
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;
|
||||||
|
let colour_targets: &[SDL_GPUColorTargetDescription] =
|
||||||
|
&[
|
||||||
|
SDL_GPUColorTargetDescription
|
||||||
|
{
|
||||||
|
format: unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) },
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState::default(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
info.target_info.color_target_descriptions = colour_targets.as_ptr();
|
||||||
|
info.target_info.num_color_targets = colour_targets.len() as u32;
|
||||||
|
info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||||
|
info.target_info.has_depth_stencil_target = true;
|
||||||
|
|
||||||
|
info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
|
||||||
|
info.depth_stencil_state.enable_depth_test = true;
|
||||||
|
info.depth_stencil_state.enable_depth_write = true;
|
||||||
|
|
||||||
|
self.pso = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader);
|
||||||
|
}
|
||||||
|
if self.pso.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.copy_pass(|pass|
|
||||||
|
{
|
||||||
|
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_ReleaseGPUGraphicsPipeline(ctx.device, self.pso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, self.pso);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Draw pyramid 1.5 units to the left and 6 units into the camera
|
||||||
|
let mut model = Mtx::translation(-1.5, 0.0, -6.0);
|
||||||
|
model.rotate(self.rot_tri, 0.0, 1.0, 0.0);
|
||||||
|
let mut viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 12, 1, 0, 0, 0);
|
||||||
|
|
||||||
|
// Draw cube 1.5 units to the right and 7 units into the camera
|
||||||
|
model = Mtx::translation(1.5, 0.0, -7.0);
|
||||||
|
model.rotate(self.rot_quad, 1.0, 1.0, 1.0);
|
||||||
|
viewproj = self.projection * model;
|
||||||
|
SDL_PushGPUVertexUniformData(cmd, 0, viewproj.as_ptr() as *const c_void, size_of::<Mtx>() as u32);
|
||||||
|
SDL_DrawGPUIndexedPrimitives(pass, 36, 1, 12, 0, 0);
|
||||||
|
|
||||||
|
SDL_EndGPURenderPass(pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rot_tri += 0.2;
|
||||||
|
self.rot_quad -= 0.15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson5>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
278
src/rust/lesson6.rs
Normal file
278
src/rust/lesson6.rs
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
/*
|
||||||
|
* 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::pixels::SDL_FColor;
|
||||||
|
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,
|
||||||
|
u: f32, v: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const VERTICES: &'static [Vertex] =
|
||||||
|
&[
|
||||||
|
// Front Face
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, u: 0.0, v: 0.0 },
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, u: 1.0, v: 0.0 },
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 1.0, u: 1.0, v: 1.0 },
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 1.0, u: 0.0, v: 1.0 },
|
||||||
|
// Back Face
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, u: 1.0, v: 0.0 },
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: -1.0, u: 1.0, v: 1.0 },
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: -1.0, u: 0.0, v: 1.0 },
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, u: 0.0, v: 0.0 },
|
||||||
|
// Top Face
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: -1.0, u: 0.0, v: 1.0 },
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 1.0, u: 0.0, v: 0.0 },
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 1.0, u: 1.0, v: 0.0 },
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: -1.0, u: 1.0, v: 1.0 },
|
||||||
|
// Bottom Face
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, u: 1.0, v: 1.0 },
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, u: 0.0, v: 1.0 },
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, u: 0.0, v: 0.0 },
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, u: 1.0, v: 0.0 },
|
||||||
|
// Right face
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: -1.0, u: 1.0, v: 0.0 },
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: -1.0, u: 1.0, v: 1.0 },
|
||||||
|
Vertex { x: 1.0, y: 1.0, z: 1.0, u: 0.0, v: 1.0 },
|
||||||
|
Vertex { x: 1.0, y: -1.0, z: 1.0, u: 0.0, v: 0.0 },
|
||||||
|
// Left Face
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: -1.0, u: 0.0, v: 0.0 },
|
||||||
|
Vertex { x: -1.0, y: -1.0, z: 1.0, u: 1.0, v: 0.0 },
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: 1.0, u: 1.0, v: 1.0 },
|
||||||
|
Vertex { x: -1.0, y: 1.0, z: -1.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
|
||||||
|
];
|
||||||
|
|
||||||
|
struct Lesson6
|
||||||
|
{
|
||||||
|
pso: *mut SDL_GPUGraphicsPipeline,
|
||||||
|
vtx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
idx_buffer: *mut SDL_GPUBuffer,
|
||||||
|
sampler: *mut SDL_GPUSampler,
|
||||||
|
texture: *mut SDL_GPUTexture,
|
||||||
|
projection: Mtx,
|
||||||
|
|
||||||
|
rot: (f32, f32, f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
//FIXME: remove when `raw_ptr_default`
|
||||||
|
impl Default for Lesson6
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
pso: null_mut(),
|
||||||
|
vtx_buffer: null_mut(),
|
||||||
|
idx_buffer: null_mut(),
|
||||||
|
sampler: null_mut(),
|
||||||
|
texture: null_mut(),
|
||||||
|
projection: Mtx::IDENTITY,
|
||||||
|
|
||||||
|
rot: (0.0, 0.0, 0.0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppImplementation for Lesson6
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's Texture Mapping 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, 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 info = SDL_GPUGraphicsPipelineCreateInfo::default();
|
||||||
|
info.vertex_shader = vertex_shader;
|
||||||
|
info.fragment_shader = fragment_shader;
|
||||||
|
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;
|
||||||
|
let colour_targets: &[SDL_GPUColorTargetDescription] =
|
||||||
|
&[
|
||||||
|
SDL_GPUColorTargetDescription
|
||||||
|
{
|
||||||
|
format: unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) },
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState::default(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
info.target_info.color_target_descriptions = colour_targets.as_ptr();
|
||||||
|
info.target_info.num_color_targets = colour_targets.len() as u32;
|
||||||
|
info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||||
|
info.target_info.has_depth_stencil_target = true;
|
||||||
|
|
||||||
|
info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
|
||||||
|
info.depth_stencil_state.enable_depth_test = true;
|
||||||
|
info.depth_stencil_state.enable_depth_write = true;
|
||||||
|
|
||||||
|
self.pso = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader);
|
||||||
|
}
|
||||||
|
if self.pso.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create texture sampler
|
||||||
|
let mut sampler_info = SDL_GPUSamplerCreateInfo::default();
|
||||||
|
sampler_info.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||||
|
sampler_info.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||||
|
self.sampler = unsafe { SDL_CreateGPUSampler(ctx.device, &sampler_info).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_CreateGPUSampler"))?;
|
||||||
|
|
||||||
|
ctx.copy_pass(|pass|
|
||||||
|
{
|
||||||
|
self.texture = pass.load_texture("Data/NeHe.bmp", true, false)?;
|
||||||
|
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);
|
||||||
|
SDL_ReleaseGPUSampler(ctx.device, self.sampler);
|
||||||
|
SDL_ReleaseGPUGraphicsPipeline(ctx.device, self.pso);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, self.pso);
|
||||||
|
|
||||||
|
// Bind texture
|
||||||
|
let texture_binding = SDL_GPUTextureSamplerBinding { texture: self.texture, sampler: self.sampler };
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Move cube 5 units into the screen and apply some rotations
|
||||||
|
let mut model = Mtx::translation(0.0, 0.0, -5.0);
|
||||||
|
model.rotate(self.rot.0, 1.0, 0.0, 0.0);
|
||||||
|
model.rotate(self.rot.1, 0.0, 1.0, 0.0);
|
||||||
|
model.rotate(self.rot.2, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct Uniforms { model_view_proj: Mtx, color: [f32; 4] }
|
||||||
|
|
||||||
|
// Push shader uniforms
|
||||||
|
let u = Uniforms { model_view_proj: self.projection * model, color: [1.0; 4] };
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rot.0 += 0.3;
|
||||||
|
self.rot.1 += 0.2;
|
||||||
|
self.rot.2 += 0.4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson6>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
381
src/rust/lesson7.rs
Normal file
381
src/rust/lesson7.rs
Normal file
@@ -0,0 +1,381 @@
|
|||||||
|
/*
|
||||||
|
* 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_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 Lesson7
|
||||||
|
{
|
||||||
|
pso_unlit: *mut SDL_GPUGraphicsPipeline,
|
||||||
|
pso_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,
|
||||||
|
light: Light,
|
||||||
|
filter: usize,
|
||||||
|
|
||||||
|
rot: (f32, f32),
|
||||||
|
speed: (f32, f32),
|
||||||
|
z: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Lesson7
|
||||||
|
{
|
||||||
|
fn default() -> Self
|
||||||
|
{
|
||||||
|
Self
|
||||||
|
{
|
||||||
|
pso_unlit: null_mut(),
|
||||||
|
pso_light: null_mut(),
|
||||||
|
vtx_buffer: null_mut(),
|
||||||
|
idx_buffer: null_mut(),
|
||||||
|
samplers: [null_mut(); 3],
|
||||||
|
texture: null_mut(),
|
||||||
|
projection: Mtx::IDENTITY,
|
||||||
|
|
||||||
|
lighting: 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 Lesson7
|
||||||
|
{
|
||||||
|
const TITLE: &'static str = "NeHe's Textures, Lighting & Keyboard 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;
|
||||||
|
let colour_targets: &[SDL_GPUColorTargetDescription] =
|
||||||
|
&[
|
||||||
|
SDL_GPUColorTargetDescription
|
||||||
|
{
|
||||||
|
format: unsafe { SDL_GetGPUSwapchainTextureFormat(ctx.device, ctx.window) },
|
||||||
|
blend_state: SDL_GPUColorTargetBlendState::default(),
|
||||||
|
}
|
||||||
|
];
|
||||||
|
info.target_info.color_target_descriptions = colour_targets.as_ptr();
|
||||||
|
info.target_info.num_color_targets = colour_targets.len() as u32;
|
||||||
|
info.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D16_UNORM;
|
||||||
|
info.target_info.has_depth_stencil_target = true;
|
||||||
|
|
||||||
|
info.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_LESS_OR_EQUAL;
|
||||||
|
info.depth_stencil_state.enable_depth_test = true;
|
||||||
|
info.depth_stencil_state.enable_depth_write = true;
|
||||||
|
|
||||||
|
// Create unlit pipeline
|
||||||
|
info.vertex_shader = vertex_shader_unlit;
|
||||||
|
info.fragment_shader = fragment_shader_unlit;
|
||||||
|
self.pso_unlit = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader_unlit);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader_unlit);
|
||||||
|
}
|
||||||
|
if self.pso_unlit.is_null()
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_CreateGPUGraphicsPipeline");
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader_light);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader_light);
|
||||||
|
}
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create lit pipeline
|
||||||
|
info.vertex_shader = vertex_shader_light;
|
||||||
|
info.fragment_shader = fragment_shader_light;
|
||||||
|
self.pso_light = unsafe { SDL_CreateGPUGraphicsPipeline(ctx.device, &info) };
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, fragment_shader_light);
|
||||||
|
SDL_ReleaseGPUShader(ctx.device, vertex_shader_light);
|
||||||
|
}
|
||||||
|
if self.pso_light.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUGraphicsPipeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create texture samplers
|
||||||
|
let create_sampler = |filter: SDL_GPUFilter, enable_mip: bool| -> Result<&mut SDL_GPUSampler, NeHeError>
|
||||||
|
{
|
||||||
|
let mut sampler_info: SDL_GPUSamplerCreateInfo = unsafe { std::mem::zeroed() };
|
||||||
|
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/Crate.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_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, if self.lighting { self.pso_light } else { self.pso_unlit });
|
||||||
|
|
||||||
|
// 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 u = Uniforms { model_view_proj: self.projection * model, color: [1.0; 4] };
|
||||||
|
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_F if down => self.filter = (self.filter + 1) % self.samplers.len(),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() -> Result<ExitCode, Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
run::<Lesson7>()?;
|
||||||
|
Ok(ExitCode::SUCCESS)
|
||||||
|
}
|
||||||
107
src/rust/nehe/application.rs
Normal file
107
src/rust/nehe/application.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
use crate::context::NeHeContext;
|
||||||
|
use crate::application::config::AppImplementation;
|
||||||
|
use crate::error::NeHeError;
|
||||||
|
use sdl3_sys::events::*;
|
||||||
|
use sdl3_sys::gpu::*;
|
||||||
|
use sdl3_sys::init::{SDL_Init, SDL_Quit, SDL_INIT_VIDEO};
|
||||||
|
use sdl3_sys::keycode::{SDLK_ESCAPE, SDLK_F1};
|
||||||
|
use std::mem::MaybeUninit;
|
||||||
|
use std::ptr::addr_of_mut;
|
||||||
|
use sdl3_sys::video::{SDL_GetWindowSizeInPixels, SDL_SetWindowFullscreen};
|
||||||
|
|
||||||
|
pub fn run<App: AppImplementation + Default + 'static>() -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
// Initialise SDL
|
||||||
|
if !unsafe { SDL_Init(SDL_INIT_VIDEO) }
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_Init"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialise GPU context
|
||||||
|
let mut ctx = NeHeContext::init(App::TITLE, App::WIDTH, App::HEIGHT)?;
|
||||||
|
|
||||||
|
// Handle depth buffer texture creation if requested
|
||||||
|
if App::CREATE_DEPTH_BUFFER != SDL_GPU_TEXTUREFORMAT_INVALID
|
||||||
|
{
|
||||||
|
let (mut backbuf_width, mut backbuf_height) = (0, 0);
|
||||||
|
unsafe { SDL_GetWindowSizeInPixels(ctx.window, &mut backbuf_width, &mut backbuf_height) };
|
||||||
|
ctx.setup_depth_texture(backbuf_width as u32, backbuf_height as u32, App::CREATE_DEPTH_BUFFER, 1.0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start application
|
||||||
|
let mut app = App::default();
|
||||||
|
app.init(&ctx)?;
|
||||||
|
|
||||||
|
let mut fullscreen = false;
|
||||||
|
|
||||||
|
'quit: loop
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
let mut event: SDL_Event = std::mem::zeroed();
|
||||||
|
while SDL_PollEvent(addr_of_mut!(event))
|
||||||
|
{
|
||||||
|
match SDL_EventType(event.r#type)
|
||||||
|
{
|
||||||
|
SDL_EVENT_QUIT => break 'quit,
|
||||||
|
SDL_EVENT_WINDOW_ENTER_FULLSCREEN => fullscreen = true,
|
||||||
|
SDL_EVENT_WINDOW_LEAVE_FULLSCREEN => fullscreen = false,
|
||||||
|
SDL_EVENT_KEY_DOWN | SDL_EVENT_KEY_UP => match event.key.key
|
||||||
|
{
|
||||||
|
SDLK_ESCAPE if event.key.down => break 'quit,
|
||||||
|
SDLK_F1 if event.key.down => _ = SDL_SetWindowFullscreen(ctx.window, !fullscreen),
|
||||||
|
_ => app.key(&ctx, event.key.key, event.key.down, event.key.repeat)
|
||||||
|
}
|
||||||
|
SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED => app.resize(&ctx, event.window.data1, event.window.data2),
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let cmd = unsafe { SDL_AcquireGPUCommandBuffer(ctx.device) };
|
||||||
|
if cmd.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_AcquireGPUCommandBuffer"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut swapchain_texture_out = MaybeUninit::<*mut SDL_GPUTexture>::uninit();
|
||||||
|
let (mut swapchain_width, mut swapchain_height) = (0, 0);
|
||||||
|
if !unsafe { SDL_WaitAndAcquireGPUSwapchainTexture(cmd, ctx.window,
|
||||||
|
swapchain_texture_out.as_mut_ptr(), &mut swapchain_width, &mut swapchain_height) }
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_WaitAndAcquireGPUSwapchainTexture");
|
||||||
|
unsafe { SDL_CancelGPUCommandBuffer(cmd); }
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let swapchain_texture = unsafe { swapchain_texture_out.assume_init() };
|
||||||
|
if swapchain_texture.is_null()
|
||||||
|
{
|
||||||
|
unsafe { SDL_CancelGPUCommandBuffer(cmd); }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if App::CREATE_DEPTH_BUFFER != SDL_GPU_TEXTUREFORMAT_INVALID
|
||||||
|
&& !ctx.depth_texture.is_null()
|
||||||
|
&& ctx.depth_texture_size != (swapchain_width, swapchain_height)
|
||||||
|
{
|
||||||
|
ctx.setup_depth_texture(swapchain_width, swapchain_height, App::CREATE_DEPTH_BUFFER, 1.0)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.draw(&ctx, cmd, swapchain_texture);
|
||||||
|
unsafe { SDL_SubmitGPUCommandBuffer(cmd); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup & quit
|
||||||
|
app.quit(&ctx);
|
||||||
|
drop(ctx);
|
||||||
|
unsafe { SDL_Quit() };
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
24
src/rust/nehe/application/config.rs
Normal file
24
src/rust/nehe/application/config.rs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
use sdl3_sys::gpu::{SDL_GPUCommandBuffer, SDL_GPUTexture, SDL_GPUTextureFormat, SDL_GPU_TEXTUREFORMAT_INVALID};
|
||||||
|
use sdl3_sys::keycode::SDL_Keycode;
|
||||||
|
use crate::context::NeHeContext;
|
||||||
|
use crate::error::NeHeError;
|
||||||
|
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait AppImplementation
|
||||||
|
{
|
||||||
|
const TITLE: &'static str;
|
||||||
|
const WIDTH: i32;
|
||||||
|
const HEIGHT: i32;
|
||||||
|
const CREATE_DEPTH_BUFFER: SDL_GPUTextureFormat = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||||
|
|
||||||
|
fn init(&mut self, ctx: &NeHeContext) -> Result<(), NeHeError> { Ok(()) }
|
||||||
|
fn quit(&mut self, ctx: &NeHeContext) {}
|
||||||
|
fn resize(&mut self, ctx: &NeHeContext, width: i32, height: i32) {}
|
||||||
|
fn draw(&mut self, ctx: &NeHeContext, cmd: *mut SDL_GPUCommandBuffer, swapchain: *mut SDL_GPUTexture);
|
||||||
|
fn key(&mut self, ctx: &NeHeContext, key: SDL_Keycode, down: bool, repeat: bool) {}
|
||||||
|
}
|
||||||
259
src/rust/nehe/context.rs
Normal file
259
src/rust/nehe/context.rs
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
mod copypass;
|
||||||
|
|
||||||
|
use crate::context::copypass::NeHeCopyPass;
|
||||||
|
use crate::error::NeHeError;
|
||||||
|
use sdl3_sys::filesystem::SDL_GetBasePath;
|
||||||
|
use sdl3_sys::gpu::*;
|
||||||
|
use sdl3_sys::properties::{SDL_CreateProperties, SDL_DestroyProperties, SDL_SetFloatProperty};
|
||||||
|
use sdl3_sys::video::*;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::ptr::{null, null_mut};
|
||||||
|
use std::str::from_utf8;
|
||||||
|
|
||||||
|
pub struct NeHeContext
|
||||||
|
{
|
||||||
|
pub window: *mut SDL_Window,
|
||||||
|
pub device: *mut SDL_GPUDevice,
|
||||||
|
pub depth_texture: *mut SDL_GPUTexture,
|
||||||
|
pub depth_texture_size: (u32, u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeHeContext
|
||||||
|
{
|
||||||
|
#[allow(unsafe_op_in_unsafe_fn)]
|
||||||
|
pub(in crate) fn init(title: &str, w: i32, h: i32) -> Result<Self, NeHeError>
|
||||||
|
{
|
||||||
|
// Create window
|
||||||
|
let flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||||
|
let window = unsafe
|
||||||
|
{
|
||||||
|
let title = CString::new(title).unwrap();
|
||||||
|
SDL_CreateWindow(title.as_ptr(), w, h, flags)
|
||||||
|
};
|
||||||
|
if window.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateWindow"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open GPU device
|
||||||
|
let formats = SDL_GPU_SHADERFORMAT_METALLIB | SDL_GPU_SHADERFORMAT_MSL |
|
||||||
|
SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL;
|
||||||
|
let Some(device) = (unsafe { SDL_CreateGPUDevice(formats, true, null()).as_mut() }) else
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUDevice"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attach window to the GPU device
|
||||||
|
if !unsafe { SDL_ClaimWindowForGPUDevice(device, window) }
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_ClaimWindowForGPUDevice"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable VSync
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
SDL_SetGPUSwapchainParameters(device, window,
|
||||||
|
SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
||||||
|
SDL_GPU_PRESENTMODE_VSYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self
|
||||||
|
{
|
||||||
|
window, device,
|
||||||
|
depth_texture: null_mut(),
|
||||||
|
depth_texture_size: (0, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for NeHeContext
|
||||||
|
{
|
||||||
|
fn drop(&mut self)
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
if !self.depth_texture.is_null()
|
||||||
|
{
|
||||||
|
SDL_ReleaseGPUTexture(self.device, self.depth_texture);
|
||||||
|
}
|
||||||
|
SDL_ReleaseWindowFromGPUDevice(self.device, self.window);
|
||||||
|
SDL_DestroyGPUDevice(self.device);
|
||||||
|
SDL_DestroyWindow(self.window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl NeHeContext
|
||||||
|
{
|
||||||
|
pub fn setup_depth_texture(&mut self, width: u32, height: u32, format: SDL_GPUTextureFormat, clear_depth: f32) -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
if !self.depth_texture.is_null()
|
||||||
|
{
|
||||||
|
unsafe { SDL_ReleaseGPUTexture(self.device, self.depth_texture) };
|
||||||
|
self.depth_texture = null_mut();
|
||||||
|
}
|
||||||
|
|
||||||
|
let props = unsafe { SDL_CreateProperties() };
|
||||||
|
if props == 0
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateProperties"));
|
||||||
|
}
|
||||||
|
// Workaround for https://github.com/libsdl-org/SDL/issues/10758
|
||||||
|
unsafe { SDL_SetFloatProperty(props, SDL_PROP_GPU_TEXTURE_CREATE_D3D12_CLEAR_DEPTH_FLOAT, clear_depth) };
|
||||||
|
|
||||||
|
let info = SDL_GPUTextureCreateInfo
|
||||||
|
{
|
||||||
|
r#type: SDL_GPU_TEXTURETYPE_2D,
|
||||||
|
format,
|
||||||
|
usage: SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
layer_count_or_depth: 1,
|
||||||
|
num_levels: 1,
|
||||||
|
sample_count: SDL_GPU_SAMPLECOUNT_1,
|
||||||
|
props,
|
||||||
|
};
|
||||||
|
let texture = unsafe { SDL_CreateGPUTexture(self.device, &info) };
|
||||||
|
unsafe { SDL_DestroyProperties(props) };
|
||||||
|
if texture.is_null()
|
||||||
|
{
|
||||||
|
return Err(NeHeError::sdl("SDL_CreateGPUTexture"));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.depth_texture = texture;
|
||||||
|
self.depth_texture_size = (width, height);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_shaders(&self, name: &str,
|
||||||
|
vertex_uniforms: u32,
|
||||||
|
vertex_storage: u32,
|
||||||
|
fragment_samplers: u32)
|
||||||
|
-> Result<(*mut SDL_GPUShader, *mut SDL_GPUShader), NeHeError>
|
||||||
|
{
|
||||||
|
let base = unsafe { CStr::from_ptr(SDL_GetBasePath()) };
|
||||||
|
let path = Path::new(from_utf8(base.to_bytes()).unwrap())
|
||||||
|
.join("Data/Shaders").join(name);
|
||||||
|
|
||||||
|
let mut info = ShaderProgramCreateInfo
|
||||||
|
{
|
||||||
|
format: SDL_GPU_SHADERFORMAT_INVALID,
|
||||||
|
vertex_uniforms, vertex_storage, fragment_samplers
|
||||||
|
};
|
||||||
|
|
||||||
|
let available_formats = unsafe { SDL_GetGPUShaderFormats(self.device) };
|
||||||
|
if available_formats & (SDL_GPU_SHADERFORMAT_METALLIB | SDL_GPU_SHADERFORMAT_MSL) != 0
|
||||||
|
{
|
||||||
|
if available_formats & SDL_GPU_SHADERFORMAT_METALLIB == SDL_GPU_SHADERFORMAT_METALLIB
|
||||||
|
{
|
||||||
|
if let Ok(lib) = fs::read(path.appending_ext("metallib"))
|
||||||
|
{
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_METALLIB;
|
||||||
|
return Ok((
|
||||||
|
self.load_shader_blob(&lib, &info, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain")?,
|
||||||
|
self.load_shader_blob(&lib, &info, SDL_GPU_SHADERSTAGE_FRAGMENT, "FragmentMain")?
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if available_formats & SDL_GPU_SHADERFORMAT_MSL == SDL_GPU_SHADERFORMAT_MSL
|
||||||
|
{
|
||||||
|
let src = fs::read(path.appending_ext("metal"))?;
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_MSL;
|
||||||
|
return Ok((
|
||||||
|
self.load_shader_blob(&src, &info, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain")?,
|
||||||
|
self.load_shader_blob(&src, &info, SDL_GPU_SHADERSTAGE_FRAGMENT, "FragmentMain")?
|
||||||
|
));
|
||||||
|
}
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
else if available_formats & SDL_GPU_SHADERFORMAT_SPIRV == SDL_GPU_SHADERFORMAT_SPIRV
|
||||||
|
{
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||||
|
Ok((
|
||||||
|
self.load_shader(path.appending_ext("vtx.spv"), &info, SDL_GPU_SHADERSTAGE_VERTEX, "main")?,
|
||||||
|
self.load_shader(path.appending_ext("frg.spv"), &info, SDL_GPU_SHADERSTAGE_FRAGMENT, "main")?
|
||||||
|
))
|
||||||
|
}
|
||||||
|
else if available_formats & SDL_GPU_SHADERFORMAT_DXIL == SDL_GPU_SHADERFORMAT_DXIL
|
||||||
|
{
|
||||||
|
info.format = SDL_GPU_SHADERFORMAT_DXIL;
|
||||||
|
Ok((
|
||||||
|
self.load_shader(path.appending_ext("vtx.dxb"), &info, SDL_GPU_SHADERSTAGE_VERTEX, "VertexMain")?,
|
||||||
|
self.load_shader(path.appending_ext("pxl.dxb"), &info, SDL_GPU_SHADERSTAGE_FRAGMENT, "PixelMain")?
|
||||||
|
))
|
||||||
|
}
|
||||||
|
else { Err(NeHeError::Fatal("No supported shader formats")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_shader(&self, filepath: PathBuf,
|
||||||
|
info: &ShaderProgramCreateInfo, stage: SDL_GPUShaderStage,
|
||||||
|
main: &str) -> Result<&mut SDL_GPUShader, NeHeError>
|
||||||
|
{
|
||||||
|
let data = fs::read(filepath)?;
|
||||||
|
self.load_shader_blob(&data, info, stage, main)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_shader_blob(&self, code: &Vec<u8>,
|
||||||
|
info: &ShaderProgramCreateInfo, stage: SDL_GPUShaderStage,
|
||||||
|
main: &str) -> Result<&mut SDL_GPUShader, NeHeError>
|
||||||
|
{
|
||||||
|
let Ok(entrypoint) = CString::new(main) else
|
||||||
|
{
|
||||||
|
return Err(NeHeError::Fatal("Null dereference when converting entrypoint string"));
|
||||||
|
};
|
||||||
|
let info = SDL_GPUShaderCreateInfo
|
||||||
|
{
|
||||||
|
code_size: code.len(),
|
||||||
|
code: code.as_ptr(),
|
||||||
|
entrypoint: entrypoint.as_ptr(),
|
||||||
|
format: info.format,
|
||||||
|
stage,
|
||||||
|
num_samplers: if stage == SDL_GPU_SHADERSTAGE_FRAGMENT { info.fragment_samplers } else { 0 },
|
||||||
|
num_storage_textures: 0,
|
||||||
|
num_storage_buffers: if stage == SDL_GPU_SHADERSTAGE_VERTEX { info.vertex_storage } else { 0 },
|
||||||
|
num_uniform_buffers: if stage == SDL_GPU_SHADERSTAGE_VERTEX { info.vertex_uniforms } else { 0 },
|
||||||
|
props: 0,
|
||||||
|
};
|
||||||
|
unsafe { SDL_CreateGPUShader(self.device, &info).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_CreateGPUShader"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_pass(&self, lambda: impl FnOnce(&mut NeHeCopyPass) -> Result<(), NeHeError>) -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
let mut pass = NeHeCopyPass::new(&self);
|
||||||
|
lambda(&mut pass)?;
|
||||||
|
pass.submit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ShaderProgramCreateInfo
|
||||||
|
{
|
||||||
|
format: SDL_GPUShaderFormat,
|
||||||
|
vertex_uniforms: u32,
|
||||||
|
vertex_storage: u32,
|
||||||
|
fragment_samplers: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait PathExt
|
||||||
|
{
|
||||||
|
fn appending_ext(&self, ext: &str) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathExt for PathBuf
|
||||||
|
{
|
||||||
|
fn appending_ext(&self, ext: &str) -> PathBuf
|
||||||
|
{
|
||||||
|
let mut path = self.as_os_str().to_owned();
|
||||||
|
path.push(".");
|
||||||
|
path.push(ext);
|
||||||
|
path.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/rust/nehe/context/copypass.rs
Normal file
274
src/rust/nehe/context/copypass.rs
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::context::NeHeContext;
|
||||||
|
use crate::error::NeHeError;
|
||||||
|
use sdl3_sys::filesystem::SDL_GetBasePath;
|
||||||
|
use sdl3_sys::gpu::*;
|
||||||
|
use sdl3_sys::pixels::*;
|
||||||
|
use sdl3_sys::surface::{SDL_ConvertSurface, SDL_DestroySurface, SDL_FlipSurface, SDL_LoadBMP, SDL_Surface, SDL_FLIP_VERTICAL};
|
||||||
|
use std::cmp::max;
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
pub struct NeHeCopyPass<'a>
|
||||||
|
{
|
||||||
|
ctx: &'a NeHeContext,
|
||||||
|
copies: Vec<Copy>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeHeCopyPass<'_>
|
||||||
|
{
|
||||||
|
pub fn create_buffer<E>(&mut self, usage: SDL_GPUBufferUsageFlags, elements: &[E])
|
||||||
|
-> Result<*mut SDL_GPUBuffer, NeHeError>
|
||||||
|
{
|
||||||
|
let size = (size_of::<E>() * elements.len()) as u32;
|
||||||
|
|
||||||
|
// Create data buffer
|
||||||
|
let info = SDL_GPUBufferCreateInfo { usage, size, props: 0 };
|
||||||
|
let buffer = unsafe { SDL_CreateGPUBuffer(self.ctx.device, &info).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_CreateGPUBuffer"))?;
|
||||||
|
|
||||||
|
// Create transfer buffer
|
||||||
|
let xfer_info = SDL_GPUTransferBufferCreateInfo { usage: SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, size, props: 0 };
|
||||||
|
let Some(transfer_buffer) = (unsafe { SDL_CreateGPUTransferBuffer(self.ctx.device, &xfer_info).as_mut() }) else
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_CreateGPUTransferBuffer");
|
||||||
|
unsafe { SDL_ReleaseGPUBuffer(self.ctx.device, buffer); }
|
||||||
|
return Err(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map transfer buffer and copy the data
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
let map = SDL_MapGPUTransferBuffer(self.ctx.device, transfer_buffer, false);
|
||||||
|
if map.is_null()
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_MapGPUTransferBuffer");
|
||||||
|
SDL_ReleaseGPUTransferBuffer(self.ctx.device, transfer_buffer);
|
||||||
|
SDL_ReleaseGPUBuffer(self.ctx.device, buffer);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
ptr::copy_nonoverlapping(elements.as_ptr(), map as *mut E, elements.len());
|
||||||
|
SDL_UnmapGPUTransferBuffer(self.ctx.device, transfer_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.copies.push(Copy { transfer_buffer, payload: CopyPayload::Buffer { buffer, size } });
|
||||||
|
Ok(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_texture(&mut self, resource_path: &str, flip_vertical: bool, gen_mipmaps: bool)
|
||||||
|
-> Result<&mut SDL_GPUTexture, NeHeError>
|
||||||
|
{
|
||||||
|
// Build path to resource: "{baseDir}/{resourcePath}"
|
||||||
|
let path = unsafe
|
||||||
|
{
|
||||||
|
let mut path = CString::from(CStr::from_ptr(SDL_GetBasePath())).into_bytes();
|
||||||
|
path.extend_from_slice(resource_path.as_bytes());
|
||||||
|
CString::from_vec_with_nul_unchecked(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load image into a surface
|
||||||
|
let image = unsafe { SDL_LoadBMP(path.as_ptr()).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_LoadBMP"))?;
|
||||||
|
|
||||||
|
// Flip surface if requested
|
||||||
|
if flip_vertical && !unsafe { SDL_FlipSurface(image, SDL_FLIP_VERTICAL) }
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_FlipSurface");
|
||||||
|
unsafe { SDL_DestroySurface(image); }
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload texture to the GPU
|
||||||
|
let result = self.create_texture_from_surface(image, gen_mipmaps);
|
||||||
|
unsafe { SDL_DestroySurface(image); }
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_texture_from_pixels(&mut self, pixels: &[u8], create_info: SDL_GPUTextureCreateInfo, gen_mipmaps: bool)
|
||||||
|
-> Result<&mut SDL_GPUTexture, NeHeError>
|
||||||
|
{
|
||||||
|
assert!(pixels.len() <= u32::MAX as usize);
|
||||||
|
|
||||||
|
let texture = unsafe { SDL_CreateGPUTexture(self.ctx.device, &create_info).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_CreateGPUTexture"))?;
|
||||||
|
|
||||||
|
// Create and copy image data to a transfer buffer
|
||||||
|
let xfer_info = SDL_GPUTransferBufferCreateInfo
|
||||||
|
{
|
||||||
|
usage: SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
|
||||||
|
size: pixels.len() as u32,
|
||||||
|
props: 0
|
||||||
|
};
|
||||||
|
let Some(transfer_buffer) = (unsafe { SDL_CreateGPUTransferBuffer(self.ctx.device, &xfer_info).as_mut() }) else
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_CreateGPUTransferBuffer");
|
||||||
|
unsafe { SDL_ReleaseGPUTexture(self.ctx.device, texture); }
|
||||||
|
return Err(err);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map transfer buffer and copy the data
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
let map = SDL_MapGPUTransferBuffer(self.ctx.device, transfer_buffer, false);
|
||||||
|
if map.is_null()
|
||||||
|
{
|
||||||
|
let err = NeHeError::sdl("SDL_MapGPUTransferBuffer");
|
||||||
|
SDL_ReleaseGPUTransferBuffer(self.ctx.device, transfer_buffer);
|
||||||
|
SDL_ReleaseGPUTexture(self.ctx.device, texture);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
ptr::copy_nonoverlapping(pixels.as_ptr(), map as *mut u8, pixels.len());
|
||||||
|
SDL_UnmapGPUTransferBuffer(self.ctx.device, transfer_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.copies.push(Copy { transfer_buffer, payload: CopyPayload::Texture
|
||||||
|
{
|
||||||
|
texture,
|
||||||
|
size: (create_info.width, create_info.height),
|
||||||
|
gen_mipmaps
|
||||||
|
} });
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_texture_from_surface(&mut self, surface: &mut SDL_Surface, gen_mipmaps: bool)
|
||||||
|
-> Result<&mut SDL_GPUTexture, NeHeError>
|
||||||
|
{
|
||||||
|
let mut info: SDL_GPUTextureCreateInfo = unsafe { std::mem::zeroed() };
|
||||||
|
info.r#type = SDL_GPU_TEXTURETYPE_2D;
|
||||||
|
info.format = SDL_GPU_TEXTUREFORMAT_INVALID;
|
||||||
|
info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||||
|
info.width = surface.w as u32;
|
||||||
|
info.height = surface.h as u32;
|
||||||
|
info.layer_count_or_depth = 1;
|
||||||
|
info.num_levels = 1;
|
||||||
|
|
||||||
|
let needs_convert: bool;
|
||||||
|
(needs_convert, info.format) = match surface.format
|
||||||
|
{
|
||||||
|
SDL_PIXELFORMAT_RGBA32 => (false, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM),
|
||||||
|
SDL_PIXELFORMAT_RGBA64 => (false, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM),
|
||||||
|
SDL_PIXELFORMAT_RGB565 => (false, SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM),
|
||||||
|
SDL_PIXELFORMAT_ARGB1555 => (false, SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM),
|
||||||
|
SDL_PIXELFORMAT_BGRA4444 => (false, SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM),
|
||||||
|
SDL_PIXELFORMAT_BGRA32 => (false, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM),
|
||||||
|
SDL_PIXELFORMAT_RGBA64_FLOAT => (false, SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT),
|
||||||
|
SDL_PIXELFORMAT_RGBA128_FLOAT => (false, SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT),
|
||||||
|
_ => (true, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM)
|
||||||
|
};
|
||||||
|
|
||||||
|
if gen_mipmaps
|
||||||
|
{
|
||||||
|
info.usage |= SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
|
||||||
|
info.num_levels = max(info.width, info.height).ilog2() + 1; // floor(log₂(max(𝑤,ℎ)) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if needs_convert
|
||||||
|
{
|
||||||
|
// Convert pixel format if required
|
||||||
|
let conv = unsafe { SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ABGR8888).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_ConvertSurface"))?;
|
||||||
|
let result = self.create_texture_from_pixels(
|
||||||
|
unsafe { std::slice::from_raw_parts(
|
||||||
|
conv.pixels as *const u8,
|
||||||
|
conv.pitch as usize * conv.h as usize) },
|
||||||
|
info,
|
||||||
|
gen_mipmaps);
|
||||||
|
unsafe { SDL_DestroySurface(conv); }
|
||||||
|
result
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
self.create_texture_from_pixels(
|
||||||
|
unsafe { std::slice::from_raw_parts(
|
||||||
|
surface.pixels as *const u8,
|
||||||
|
surface.pitch as usize * surface.h as usize) },
|
||||||
|
info,
|
||||||
|
gen_mipmaps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeHeCopyPass<'_>
|
||||||
|
{
|
||||||
|
pub(in crate::context) fn new(ctx: &'_ NeHeContext) -> NeHeCopyPass<'_>
|
||||||
|
{
|
||||||
|
NeHeCopyPass { ctx, copies: Vec::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::context) fn submit(&mut self, ) -> Result<(), NeHeError>
|
||||||
|
{
|
||||||
|
let cmd = unsafe { SDL_AcquireGPUCommandBuffer(self.ctx.device).as_mut() }
|
||||||
|
.ok_or(NeHeError::sdl("SDL_AcquireGPUCommandBuffer"))?;
|
||||||
|
|
||||||
|
// Begin the copy pass
|
||||||
|
let pass = unsafe { SDL_BeginGPUCopyPass(cmd) };
|
||||||
|
|
||||||
|
// Upload data into GPU buffer(s)
|
||||||
|
for copy in self.copies.iter()
|
||||||
|
{
|
||||||
|
match copy.payload
|
||||||
|
{
|
||||||
|
CopyPayload::Buffer { buffer, size } =>
|
||||||
|
{
|
||||||
|
let source = SDL_GPUTransferBufferLocation { transfer_buffer: copy.transfer_buffer, offset: 0 };
|
||||||
|
let destination = SDL_GPUBufferRegion { buffer, offset: 0, size };
|
||||||
|
unsafe { SDL_UploadToGPUBuffer(pass, &source, &destination, false); }
|
||||||
|
}
|
||||||
|
CopyPayload::Texture { texture, size, .. } =>
|
||||||
|
{
|
||||||
|
let mut source: SDL_GPUTextureTransferInfo = unsafe { std::mem::zeroed() };
|
||||||
|
source.transfer_buffer = copy.transfer_buffer;
|
||||||
|
source.offset = 0;
|
||||||
|
let mut destination: SDL_GPUTextureRegion = unsafe { std::mem::zeroed() };
|
||||||
|
destination.texture = texture;
|
||||||
|
destination.w = size.0;
|
||||||
|
destination.h = size.1;
|
||||||
|
destination.d = 1; // info.layer_count_or_depth
|
||||||
|
unsafe { SDL_UploadToGPUTexture(pass, &source, &destination, false); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// End the copy pass
|
||||||
|
unsafe { SDL_EndGPUCopyPass(pass); }
|
||||||
|
|
||||||
|
// Generate mipmaps if needed
|
||||||
|
self.copies.iter().for_each(|copy| match copy.payload
|
||||||
|
{
|
||||||
|
CopyPayload::Texture { texture, gen_mipmaps, .. } if gen_mipmaps
|
||||||
|
=> unsafe { SDL_GenerateMipmapsForGPUTexture(cmd, texture) },
|
||||||
|
_ => ()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Submit the command buffer
|
||||||
|
unsafe { SDL_SubmitGPUCommandBuffer(cmd); }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for NeHeCopyPass<'_>
|
||||||
|
{
|
||||||
|
fn drop(&mut self)
|
||||||
|
{
|
||||||
|
for copy in self.copies.iter().rev()
|
||||||
|
{
|
||||||
|
unsafe { SDL_ReleaseGPUTransferBuffer(self.ctx.device, copy.transfer_buffer) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Copy
|
||||||
|
{
|
||||||
|
transfer_buffer: *mut SDL_GPUTransferBuffer,
|
||||||
|
payload: CopyPayload,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CopyPayload
|
||||||
|
{
|
||||||
|
Buffer { buffer: *mut SDL_GPUBuffer, size: u32 },
|
||||||
|
Texture { texture: *mut SDL_GPUTexture, size: (u32, u32), gen_mipmaps: bool },
|
||||||
|
}
|
||||||
45
src/rust/nehe/error.rs
Normal file
45
src/rust/nehe/error.rs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{fmt, io};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use sdl3_sys::error::SDL_GetError;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NeHeError
|
||||||
|
{
|
||||||
|
Fatal(&'static str),
|
||||||
|
SDLError(&'static str, &'static CStr),
|
||||||
|
IOError(io::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NeHeError
|
||||||
|
{
|
||||||
|
pub fn sdl(fname: &'static str) -> Self
|
||||||
|
{
|
||||||
|
unsafe { NeHeError::SDLError(fname, CStr::from_ptr(SDL_GetError())) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NeHeError
|
||||||
|
{
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||||
|
{
|
||||||
|
match self
|
||||||
|
{
|
||||||
|
NeHeError::Fatal(msg) => fmt.write_str(msg),
|
||||||
|
NeHeError::SDLError(a, b) => write!(fmt, "{}: {}", a, b.to_string_lossy()),
|
||||||
|
NeHeError::IOError(e) => write!(fmt, "std::io: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for NeHeError
|
||||||
|
{
|
||||||
|
fn from(err: io::Error) -> Self { Self::IOError(err) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for NeHeError {}
|
||||||
4
src/rust/nehe/lib.rs
Normal file
4
src/rust/nehe/lib.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pub mod application;
|
||||||
|
pub mod context;
|
||||||
|
pub mod error;
|
||||||
|
pub mod matrix;
|
||||||
158
src/rust/nehe/matrix.rs
Normal file
158
src/rust/nehe/matrix.rs
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 a dinosaur
|
||||||
|
* SPDX-License-Identifier: Zlib
|
||||||
|
*/
|
||||||
|
|
||||||
|
use core::ops::Mul;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
pub struct Mtx([f32; 16]);
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Mtx
|
||||||
|
{
|
||||||
|
pub(crate) const fn new(
|
||||||
|
m00: f32, m01: f32, m02: f32, m03: f32,
|
||||||
|
m10: f32, m11: f32, m12: f32, m13: f32,
|
||||||
|
m20: f32, m21: f32, m22: f32, m23: f32,
|
||||||
|
m30: f32, m31: f32, m32: f32, m33: f32) -> Self
|
||||||
|
{
|
||||||
|
Self([
|
||||||
|
m00, m01, m02, m03,
|
||||||
|
m10, m11, m12, m13,
|
||||||
|
m20, m21, m22, m23,
|
||||||
|
m30, m31, m32, m33,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const IDENTITY: Self = Self::new(
|
||||||
|
0.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
pub const fn translation(x: f32, y: f32, z: f32) -> Self
|
||||||
|
{
|
||||||
|
Self::new(
|
||||||
|
1.0, 0.0, 0.0, 0.0,
|
||||||
|
0.0, 1.0, 0.0, 0.0,
|
||||||
|
0.0, 0.0, 1.0, 0.0,
|
||||||
|
x, y, z, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn make_rotation(c: f32, s: f32, x: f32, y: f32, z: f32) -> [f32; 9]
|
||||||
|
{
|
||||||
|
let rc = 1.0 - c;
|
||||||
|
let (rcx, rcy, rcz) = (x * rc, y * rc, z * rc);
|
||||||
|
let (sx, sy, sz) = (x * s, y * s, z * s);
|
||||||
|
|
||||||
|
[
|
||||||
|
rcx * x + c, rcy * x + sz, rcz * x - sy,
|
||||||
|
rcx * y - sz, rcy * y + c, rcz * y + sx,
|
||||||
|
rcx * z + sy, rcy * z - sx, rcz * z + c,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_gl_rotation(angle: f32, x: f32, y: f32, z: f32) -> [f32; 9]
|
||||||
|
{
|
||||||
|
// Treat inputs like glRotatef
|
||||||
|
let theta = angle.to_radians();
|
||||||
|
let axis_mag = (x * x + y * y + z * z).sqrt();
|
||||||
|
let (nx, ny, nz) =
|
||||||
|
if (axis_mag - 1.0).abs() > f32::EPSILON
|
||||||
|
{ (x / axis_mag, y / axis_mag, z / axis_mag) } else
|
||||||
|
{ (x, y, z) };
|
||||||
|
Self::make_rotation(theta.cos(), theta.sin(), nx, ny, nz)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn rotation(angle: f32, x: f32, y: f32, z: f32) -> Self
|
||||||
|
{
|
||||||
|
let r = Self::make_gl_rotation(angle, x, y, z);
|
||||||
|
Self::new(
|
||||||
|
r[0], r[1], r[2], 0.0,
|
||||||
|
r[3], r[4], r[5], 0.0,
|
||||||
|
r[6], r[7], r[8], 0.0,
|
||||||
|
0.0, 0.0, 0.0, 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn perspective(fovy: f32, aspect: f32, near: f32, far: f32) -> Self
|
||||||
|
{
|
||||||
|
let h = 1.0 / (fovy.to_radians() * 0.5);
|
||||||
|
let w = h / aspect;
|
||||||
|
let invcliprng = 1.0 / (far - near);
|
||||||
|
let zh = -(far + near) * invcliprng;
|
||||||
|
let zl = -(2.0 * far * near) * invcliprng;
|
||||||
|
|
||||||
|
/*
|
||||||
|
[w 0 0 0]
|
||||||
|
[0 h 0 0]
|
||||||
|
[0 0 zh zl]
|
||||||
|
[0 0 -1 0]
|
||||||
|
*/
|
||||||
|
Self::new(
|
||||||
|
w, 0.0, 0.0, 0.0,
|
||||||
|
0.0, h, 0.0, 0.0,
|
||||||
|
0.0, 0.0, zh, -1.0,
|
||||||
|
0.0, 0.0, zl, 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn translate(&mut self, x: f32, y: f32, z: f32)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
m = { [1 0 0 x]
|
||||||
|
[0 1 0 y]
|
||||||
|
[0 0 1 z]
|
||||||
|
[0 0 0 1] } * m
|
||||||
|
*/
|
||||||
|
let m = self.0;
|
||||||
|
self.0[12] += x * m[0] + y * m[4] + z * m[8];
|
||||||
|
self.0[13] += x * m[1] + y * m[5] + z * m[9];
|
||||||
|
self.0[14] += x * m[2] + y * m[6] + z * m[10];
|
||||||
|
self.0[15] += x * m[3] + y * m[7] + z * m[11];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate(&mut self, angle: f32, x: f32, y: f32, z: f32)
|
||||||
|
{
|
||||||
|
let r = Self::make_gl_rotation(angle, x, y, z);
|
||||||
|
|
||||||
|
// Partial matrix multiplication
|
||||||
|
let mut tmp = [0f32; 12]; // Set up temporary
|
||||||
|
tmp.copy_from_slice(&self.0[..12]);
|
||||||
|
self.0[0] = r[0] * tmp[0] + r[1] * tmp[4] + r[2] * tmp[8];
|
||||||
|
self.0[1] = r[0] * tmp[1] + r[1] * tmp[5] + r[2] * tmp[9];
|
||||||
|
self.0[2] = r[0] * tmp[2] + r[1] * tmp[6] + r[2] * tmp[10];
|
||||||
|
self.0[3] = r[0] * tmp[3] + r[1] * tmp[7] + r[2] * tmp[11];
|
||||||
|
self.0[4] = r[3] * tmp[0] + r[4] * tmp[4] + r[5] * tmp[8];
|
||||||
|
self.0[5] = r[3] * tmp[1] + r[4] * tmp[5] + r[5] * tmp[9];
|
||||||
|
self.0[6] = r[3] * tmp[2] + r[4] * tmp[6] + r[5] * tmp[10];
|
||||||
|
self.0[7] = r[3] * tmp[3] + r[4] * tmp[7] + r[5] * tmp[11];
|
||||||
|
self.0[8] = r[6] * tmp[0] + r[7] * tmp[4] + r[8] * tmp[8];
|
||||||
|
self.0[9] = r[6] * tmp[1] + r[7] * tmp[5] + r[8] * tmp[9];
|
||||||
|
self.0[10] = r[6] * tmp[2] + r[7] * tmp[6] + r[8] * tmp[10];
|
||||||
|
self.0[11] = r[6] * tmp[3] + r[7] * tmp[7] + r[8] * tmp[11];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn as_ptr(&self) -> *const f32 { self.0.as_ptr() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Mtx
|
||||||
|
{
|
||||||
|
fn default() -> Self { Self::IDENTITY }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul for Mtx
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: Self) -> Self
|
||||||
|
{
|
||||||
|
Self(core::array::from_fn(|i|
|
||||||
|
{
|
||||||
|
let (row, col) = (i & 0x3, i >> 2);
|
||||||
|
(0..4).fold(0f32, |a, j| a +
|
||||||
|
self.0[j * 4 + row] *
|
||||||
|
rhs.0[col * 4 + j])
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user