PoC renderer - #14
Conversation
…location, 15ms -> 0.4ms, 5k capsules
8720f7b to
2dd2e91
Compare
04b6f32 to
c1d7636
Compare
| @@ -0,0 +1,7 @@ | |||
| pub struct RendererBufferTag; | |||
There was a problem hiding this comment.
Is core module a good place for such tags? Can't they be somewhere in rendering-related file in engine?
There was a problem hiding this comment.
I think no, because XYZTag is for common ResourceManager implementation. I'd keep it in Core until the engine matures with a couple more demos.
Separate engine ResourceManager and RendererResourceManagers cause many headaches, duplication and additional complexities at this stage.
|
|
||
| // --- Borrow-only render query bundle and alias --- | ||
|
|
||
| use crate::ecs::{CameraComponent, ComponentStorage, EntityHandle, TransformComponent}; |
There was a problem hiding this comment.
Should be moved to the top of this file
| @@ -158,10 +159,21 @@ impl TransformComponent { | |||
| } | |||
|
|
|||
| fn get_rotation_matrix(&self) -> Matrix3<f32> { | |||
|
|
||
| } | ||
|
|
||
| pub fn update_transform_matrices(transform_component: &mut TransformComponent) { |
| } No newline at end of file | ||
|
|
||
| // Peek system timer without taking ownership (cloned snapshot) | ||
| pub fn peek_system_timer( |
There was a problem hiding this comment.
Maybe name it clone_system_timer?
I'm not used to "peek", but it may be only my thing
There was a problem hiding this comment.
Yeah, seconding that - if we clone we clone 😁
| key: render_queue_key, | ||
| entity_index: entity_handle.data().index as u32, | ||
| }; | ||
| engine.render_queue.push(render_queue_item); |
There was a problem hiding this comment.
what about sorting the queue again after this batch update?
There was a problem hiding this comment.
Sort is done in PassScene (opaques) where context of selected camera view is present.
So sort depends on the pass logic, imagine OpaquePass (front to back) vs TransparentPass (back to front) sampling queue from the same world state
| &mut timer | ||
| ) { | ||
| // Build WorldView with raw pointers to avoid borrow conflicts | ||
| let world_view = { |
There was a problem hiding this comment.
using pointers in rust is an antipattern.
You should use splitting borrow pattern (disjoined borrows)
Always prefer passing the ownership
There was a problem hiding this comment.
As I've already discovered :P
Pls help C/C++ dev, that wants code fast pixels, with these Rust Shenanigans
There was a problem hiding this comment.
Yeah, basically arm yourself with a gun:
https://doc.rust-lang.org/nomicon/borrow-splitting.html
| } No newline at end of file | ||
| } | ||
|
|
||
| fn init_default_resources(engine: &mut Engine) -> Result<(), Error> { |
There was a problem hiding this comment.
should be in renderer.rs like all bootstraping logic IMO
| @@ -1,25 +1,47 @@ | |||
| #![cfg_attr(debug_assertions, allow(dead_code, unused_imports))] | |||
There was a problem hiding this comment.
General thoughts about proposed shader passess architecture.
Right now all materials are dynamic. Shader resources read external shader files, figure out what uniforms and samplers they need, and then create pipeline objects (descriptors) with the appropriate fields.
Users still have to define shader parameters manually, but we can add proper reflection so this becomes fully automatic.
With this setup, we don’t need to hardcode render passes as Rust structs with fixed fields. I initially explored that approach but ended up going with generic dynamic objects instead.
Additional points / concerns:
- I really don’t like the idea of having shader code embedded directly in the engine.
It breaks normal workflows (syntax highlighting, tooling, error messages, file diffs, etc.), and it forces engine rebuilds every time you tweak a shader. - IMO we should stick to HLSL, because that’s the market standard. Anything else immediately scares people off (including myself! (while developing this engine I would like to learn shader development as well, but not in <1% market share shader language)). Same situation as wanting to use Rhai (which is great) but picking Lua because that’s what devs expect, unfortunately.
- A hybrid setup with Rust-GPU + HLSL is too messy and confusing. Two shader languages doubles the mental overhead.
- Not even sure if Rust-GPU supports hot reload in a practical way. Tooling around it is still pretty limited.
- External shader files give us all the benefits: hot reload, version control diffs, modding support, cleaner debugging.
- Most graphics programmers already know HLSL. Keeping things familiar makes onboarding way easier.
There was a problem hiding this comment.
Also we may end up with an explosion for render pass code files that will duplicate a lot of the same logic (setting up the descriptors, etc)
It looks like this is already happening.
There was a problem hiding this comment.
I really don’t like the idea of having shader code embedded directly in the engine.
It breaks normal workflows (syntax highlighting, tooling, error messages, file diffs, etc.), and it forces engine rebuilds every time you tweak a shader.
I don't like it too, moreover, It's not put into a proper place yet, and it can be put into files etc.
There was a problem hiding this comment.
IMO we should stick to HLSL, because that’s the market standard. Anything else immediately scares people off (including myself! (while developing this engine I would like to learn shader development as well, but not in <1% market share shader language)). Same situation as wanting to use Rhai (which is great) but picking Lua because that’s what devs expect, unfortunately.
Huh? I don't even know how currently used shader language is called, I just went with the existing flow. I've spent more time with GLSL and I've never heard of HLSL being any standard apart from DirectX APIs (which AAA engines may like due to M$ support on XBox).
I do not care as long as the shader compilation pipeline (SPIRV) is simple for targeted platforms (x64, arm, web)
There was a problem hiding this comment.
Also we may end up with an explosion for render pass code files that will duplicate a lot of the same logic (setting up the descriptors, etc)
It looks like this is already happening.
Duplication at this stage is intended. I copy-pasted with a cold blood to see what code will be the same when exploring the performant modern rendering code. This way it is easier to change before prematurely optimizing the code with complexities. To be cleaned up and deduplicated when reaching the scope of the demo. Until the feature scope is reached, I do not have enough observation to cleanup.
There was a problem hiding this comment.
If this is a living PR, then even better - we can discuss, iterate and conclude with much better code. I cannot add more because I am not a rendering expert. My aim would be to keep it optimized/quite simple (not intimidating with complexity) - something Aaltonen managed to achieve in his presentation.
| #[derive(Copy, Clone)] | ||
| // HOT(ish): per-material UBO bound during draws; keep compact (<= 64B ideal). | ||
| // Layout is 3x vec3+pads → 48 bytes total, 16-byte aligned to match WGSL/std140 rules. | ||
| pub struct RendererMaterialParamsStd140 { |
There was a problem hiding this comment.
I know that structs with shader parameters are more elegant...
I was researching this topic and ultimately went with accessing material parameters via their names as strings.
This is the approach that both Unity and Unreal use. It is the most flexible one allowing to have generic material objects instead of hardcoded ones.
This way we can have PBR material, cartoon material and skybox material declared as the same material/shader type, all automatic/dynamic. No separate mental-overhead (engine-hardcoded!) structs required.
Current approach to this is not a random one, it is intentional and long considered.
There was a problem hiding this comment.
PoC system is based on IdTech6 and HypeHype. The core pass PBR that is most minimal and performant.
Unreal explodes with shader combinations which I dislike as an architecture choice and Unity well... they tried.
But hey, we have encapsulated Passes, so 80% of shaders can benefit from core, fast pipeline. For remaining others 20% we can provide the custom path with string lookups and other thing but do not quote us on performance numbers there.
I like that in Unity I can create an experiment shader and drop on an object - this will be custom pass.
Unreal has PBR materials on nodes that I hate, however PoC PassScene can be extended with similar PBR material interface, however when you know what you are doing, IdTech6 did UberShader for easier low-level shader execution tuning.
If you really want to draw 100% meshes customized, feel free to copy-paste our performant baseline Pass and modify as you like.
| @@ -0,0 +1,16 @@ | |||
| [build] | |||
| # Enable SIMD optimizations for math operations | |||
| # This will auto-vectorize cgmath operations when possible | |||
There was a problem hiding this comment.
Please rebase on glam - we probably get auto-vectorization from the get-go
| rustflags = ["-C", "target-cpu=native"] | ||
|
|
||
| # Alternative: More conservative SIMD flags | ||
| # rustflags = ["-C", "target-feature=+avx2,+fma"] |
There was a problem hiding this comment.
sometimes we might want avx512 etc
| use std::fmt::Formatter; | ||
| use core::fmt::Debug; | ||
| use std::iter::Enumerate; | ||
| use std::num::{ NonZeroU32}; |
| self.counters.insert(label.into(), value); | ||
| } | ||
|
|
||
| // pub fn increment_counter(&mut self, label: impl Into<String>, delta: u64) { |
There was a problem hiding this comment.
Let's not commit commented out code
| use cgmath::{Deg, Matrix3, SquareMatrix, Zero}; | ||
| use anyhow::{ Result, Context, Error }; | ||
| use serde::{ Serialize, Deserialize }; | ||
| use glam::{Mat3, Mat4, Quat, Vec3}; |
There was a problem hiding this comment.
Please use pill_core names for types since we already merged #15
|
|
||
| // Normal matrix: rotation only | ||
| let n = Mat3::from_quat(q); | ||
| transform_component.normal_matrix = n.to_cols_array_2d(); |
There was a problem hiding this comment.
I think we already talked about that - please also check if what I replaced in #15 is faster or if this is, just merge the faster alternatives 😄
| @@ -0,0 +1,241 @@ | |||
| # Pill Renderer vNext (wgpu) – Minimal MVP, API-Correct | |||
There was a problem hiding this comment.
Can we move all documentation related entities to docs - even better - annotate the code with documentation?
| @@ -0,0 +1,10 @@ | |||
| Building game project from /Users/mk/dev/demo/Pill-Demo... | |||
There was a problem hiding this comment.
do we need the log file staged?
| crates/pill_standalone/Cargo.toml | ||
| examples/*/Cargo.toml | ||
|
|
||
| engine/Cargo.toml |
There was a problem hiding this comment.
I know why you are doing that, though this is probably not the best solution 😓
The launcher will constantly update the paths in engine/Cargo and show this file as changed. Honestly did not find a good solution for that except calling git add engine/pill_* when staging files.
| env_tex_rt, | ||
| prefilter_handle, | ||
| ibl_brdf_rt, | ||
| 5, |
There was a problem hiding this comment.
in general we have a lot of magic constants - I know it is very math-oriented and settings-oriented code, but we still should strive to have as many documented variables as possible. Even better - having one simple place to tweak them (even later we could allow for tweaking at runtime if some of them are changing the visual output of the system).
| 5, | ||
| ); | ||
| { | ||
| let self_ptr: *mut dyn crate::graphics::PillRenderer = &mut *engine.renderer; |
There was a problem hiding this comment.
I would put the imports at the top of the file and call relative imports instead of absolute ones.
| pub struct MaterialDesc<'a> { | ||
| pub label: &'a str, | ||
| // Factors | ||
| pub albedo: [f32; 3], |
There was a problem hiding this comment.
Maybe use SIMD types?
There was a problem hiding this comment.
Materials will not be involved in much CPU compute. Most often set value, copy to WGPU buffer and upload to GPU (mem bandwidth). On GPU shaders do heavy ALU operations.
So if anything can be squeezed then the material precision -> size of GPU buffer / cache line fit in the loop over many materials.
| fn resize(&mut self, new_window_size: winit::dpi::PhysicalSize<u32>); | ||
|
|
||
| fn destroy_texture(&mut self, renderer_texture_handle: RendererTextureHandle) -> Result<()>; | ||
| // Creates a 256B-aligned uniform buffer (COPY_DST) and returns its handle |
There was a problem hiding this comment.
Yes, the intent is to be able to create small buffers fitting the cache, suitable for the hot path. Another that on Mac, Metal failed to allocate buffers smaller than 256B, hence the padding alignment for the time being. To be checked on other platforms.
|
|
||
| // WorldView raw-pointer based view to avoid borrow conflicts at call site | ||
| pub struct WorldView { | ||
| pub active_camera: crate::ecs::EntityHandle, |
There was a problem hiding this comment.
same as above, can we use Rust's no-pointer approach here?
cbb389c to
4500973
Compare
c5da079 to
b324f33
Compare

Benchmarked with 60k dynamic pills (30k -> 60k since previous benchmark), 30 FPS @ 1920x1200 on Mac M1.
https://youtu.be/o140PuAGH2k
Reference: HypeHype Mobile Rendering Architecture, Aaltonen23
PS: Please mind that it's my first time with Rust