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