Instanced GPU-skinning subsystem. One per renderer.
More...
#include <SkinnedRenderer.hpp>
|
| | SkinnedRenderer ()=default |
| | ~SkinnedRenderer ()=default |
| | SkinnedRenderer (const SkinnedRenderer &)=delete |
| SkinnedRenderer & | operator= (const SkinnedRenderer &)=delete |
| | SkinnedRenderer (SkinnedRenderer &&)=delete |
| SkinnedRenderer & | operator= (SkinnedRenderer &&)=delete |
| void | init (SDL_GPUDevice *device, SDL_GPUTextureFormat &colorTarget, const SDL_GPUShaderFormat &shaderFormat) |
| | Bind the SDL GPU device.
|
| void | setFrustumCullEnabled (bool enabled) |
| | Enable/disable per-instance frustum culling in setFrame.
|
| bool | setRig (const std::vector< RigMeshSource > &meshes, int numJoints) |
| | Install the shared character rig.
|
| void | setFrame (const std::vector< glm::mat4 > &palette, const std::vector< SkinnedInstance > &instances, const FrustumPlanes &frustum) |
| | Push this frame's per-character bone palette + per-instance data and frustum-cull it down to the on-screen subset.
|
| void | uploadFrame (SDL_GPUCommandBuffer *cmd, SDL_GPUCopyPass *copyPass) |
| | Upload this frame's palette + instance buffers to the GPU.
|
| void | draw (SDL_GPURenderPass *renderPass, SDL_GPUCommandBuffer *cmd) |
| | Issue the instanced draws for every visible skinned character.
|
| void | drawDepth (SDL_GPURenderPass *renderPass, SDL_GPUCommandBuffer *cmd) |
| | Issue the instanced draws for every visible skinned character into a depth-only shadow pass.
|
| void | drawChams (SDL_GPURenderPass *renderPass, SDL_GPUCommandBuffer *cmd) |
| | Draw wallhack chams for flagged instances.
|
| void | drawKillcamHighlight (SDL_GPURenderPass *renderPass, SDL_GPUCommandBuffer *cmd) |
| | Draw the killcam killer full-body red highlight.
|
| void | shutdown () |
| | Release all GPU resources owned by this subsystem.
|
| int | numJoints () const |
| | Number of joints in the installed rig (0 if not installed).
|
| bool | rigInstalled () const |
| | True after a successful setRig.
|
| size_t | pendingInstanceCount () const |
| | Number of instances pending render this frame (0 if no frame submitted).
|
|
| static bool | sphereInFrustum (const glm::vec3 ¢er, float radius, const FrustumPlanes &planes) |
| | Test whether a world-space bounding sphere intersects (or lies inside) the view frustum.
|
Instanced GPU-skinning subsystem. One per renderer.
◆ SkinnedRenderer() [1/3]
| SkinnedRenderer::SkinnedRenderer |
( |
| ) |
|
|
default |
◆ ~SkinnedRenderer()
| SkinnedRenderer::~SkinnedRenderer |
( |
| ) |
|
|
default |
◆ SkinnedRenderer() [2/3]
◆ SkinnedRenderer() [3/3]
◆ createChamsPipeline()
| bool SkinnedRenderer::createChamsPipeline |
( |
| ) |
|
|
private |
Create the wallhack chams pipeline.
◆ createKillcamHighlightPipeline()
| bool SkinnedRenderer::createKillcamHighlightPipeline |
( |
| ) |
|
|
private |
Create the killcam full-body highlight pipeline.
◆ createSkinnedDepthPipeline()
| bool SkinnedRenderer::createSkinnedDepthPipeline |
( |
const SDL_GPUShaderFormat & | shaderFormat | ) |
|
|
private |
◆ createSkinningPipeline()
| bool SkinnedRenderer::createSkinningPipeline |
( |
SDL_GPUTextureFormat & | colorTarget, |
|
|
const SDL_GPUShaderFormat & | shaderFormat ) |
|
private |
◆ draw()
| void SkinnedRenderer::draw |
( |
SDL_GPURenderPass * | renderPass, |
|
|
SDL_GPUCommandBuffer * | cmd ) |
Issue the instanced draws for every visible skinned character.
- Parameters
-
| renderPass | The active main HDR (or swapchain) render pass. |
| cmd | The frame command buffer. |
Called from NewRenderer::drawFrame INSIDE the geometry pass. Currently a no-op placeholder — see cpp for the algorithm sketch.
◆ drawChams()
| void SkinnedRenderer::drawChams |
( |
SDL_GPURenderPass * | renderPass, |
|
|
SDL_GPUCommandBuffer * | cmd ) |
Draw wallhack chams for flagged instances.
Material id 2 renders flat-red with a GREATER depth test, so only the parts occluded by world geometry show through. Must be called BEFORE draw() so the depth buffer still holds world-only geometry.
◆ drawDepth()
| void SkinnedRenderer::drawDepth |
( |
SDL_GPURenderPass * | renderPass, |
|
|
SDL_GPUCommandBuffer * | cmd ) |
Issue the instanced draws for every visible skinned character into a depth-only shadow pass.
- Parameters
-
| renderPass | The active shadow depth render pass. |
| cmd | The frame command buffer. |
Mirrors draw() but binds the depth-only pipeline so the player rig is rasterised into the shadow map (and thus casts shadows). Relies on the caller having already pushed the shadow view-projection at vertex UBO slot 0 (NewRenderer::drawGeometryDepthPass does this).
◆ drawKillcamHighlight()
| void SkinnedRenderer::drawKillcamHighlight |
( |
SDL_GPURenderPass * | renderPass, |
|
|
SDL_GPUCommandBuffer * | cmd ) |
Draw the killcam killer full-body red highlight.
Material id 1 renders after the normal skinned pass so the killer body is visibly red in the killcam, matching the weapon highlight.
◆ ensureSsbos()
| bool SkinnedRenderer::ensureSsbos |
( |
Uint32 | paletteBytes, |
|
|
Uint32 | instanceBytes ) |
|
private |
Grow palette/instance SSBOs (and their transfer buffers) to at least these byte sizes.
◆ init()
| void SkinnedRenderer::init |
( |
SDL_GPUDevice * | device, |
|
|
SDL_GPUTextureFormat & | colorTarget, |
|
|
const SDL_GPUShaderFormat & | shaderFormat ) |
Bind the SDL GPU device.
Call from NewRenderer::init once the device exists. Does NOT allocate any GPU resources yet; those are created lazily on the first setRig / setFrame call.
- Parameters
-
◆ numJoints()
| int SkinnedRenderer::numJoints |
( |
| ) |
const |
|
inlinenodiscard |
Number of joints in the installed rig (0 if not installed).
◆ operator=() [1/2]
◆ operator=() [2/2]
◆ pendingInstanceCount()
| size_t SkinnedRenderer::pendingInstanceCount |
( |
| ) |
const |
|
inlinenodiscard |
Number of instances pending render this frame (0 if no frame submitted).
◆ rigInstalled()
| bool SkinnedRenderer::rigInstalled |
( |
| ) |
const |
|
inlinenodiscard |
True after a successful setRig.
◆ setFrame()
| void SkinnedRenderer::setFrame |
( |
const std::vector< glm::mat4 > & | palette, |
|
|
const std::vector< SkinnedInstance > & | instances, |
|
|
const FrustumPlanes & | frustum ) |
Push this frame's per-character bone palette + per-instance data and frustum-cull it down to the on-screen subset.
- Parameters
-
| palette | Flat array, sized numInstances * numJoints (mat4 each). |
| instances | One entry per character (ALL of them — visible or not). paletteBase of entry i MUST equal i * numJoints so the palette slice for that instance can be located. |
| frustum | Camera view-projection frustum planes (Gribb-Hartmann). Each instance is bounding-sphere tested against these and dropped if fully outside the view. |
IMPLEMENTATION (real, wired):
- Sphere-culls instances against frustum: the bounding sphere is the rig's bind-pose bounds (computed in setRig) transformed by the instance worldTransform, so the test tracks the actual rendered mesh (which is vertically offset from the sim position) rather than a bare point — this is what fixes characters popping out at the screen edge.
- Copies the surviving instances (and their palette slices, compacted so no off-screen palettes are uploaded) into CPU-side staging (framePalette_, frameInstances_).
- The actual GPU upload happens later via uploadFrame() (called by NewRenderer::drawFrame before the render pass starts).
DATA SOURCE: Game.cpp's per-frame animation block walks the ECS view <AnimatedCharacter, Position, Velocity, PlayerVisState, InputSnapshot>:
- For each character compute worldTransform = T(pos) * R(yaw) * S(scale).
- palette[slot * numJoints .. (slot+1) * numJoints] = animator.skinMatrices().
- instances[slot] = {worldTransform, paletteBase=slot*numJoints, tint}.
CONSUMER: the skinned vertex shader reads: instances[gl_InstanceIndex] → worldTransform + paletteBase palette[paletteBase + boneIndices.k] → bone matrix k (k = 0..3)
◆ setFrustumCullEnabled()
| void SkinnedRenderer::setFrustumCullEnabled |
( |
bool | enabled | ) |
|
|
inline |
Enable/disable per-instance frustum culling in setFrame.
Defaults to true.
◆ setRig()
| bool SkinnedRenderer::setRig |
( |
const std::vector< RigMeshSource > & | meshes, |
|
|
int | numJoints ) |
Install the shared character rig.
Call ONCE after init.
- Parameters
-
| meshes | One source-mesh entry per skinned mesh in the rig (typically 1-3 for humanoid rigs). |
| numJoints | Number of skeleton joints. Determines per-instance palette stride in the shader. |
- Returns
- True on success. False if already installed, or upload failed.
IMPLEMENTATION (real, wired):
- Iterates meshes, creating three GPU buffers per mesh: slot 0: bind-pose vertices (ModelVertex, 48 bytes/vert) — VERTEX usage slot 1: bone influences (BoneInfluence, 32 bytes/vert) — VERTEX usage slot 2: indices (uint32_t, 4 bytes each) — INDEX usage Saves them in skinnedMeshes_.
- Stores numJoints in numJoints_; sets rigInstalled_.
- Does NOT allocate palette/instance SSBOs — those grow lazily on the first setFrame call.
DATA SOURCE: Game.cpp builds vector<RigMeshSource> from CharacterRig::meshes() (animation/CharacterRig.hpp) once after the rig FBX loads. See RigMeshSource in RendererTypes.hpp for the layout.
◆ shutdown()
| void SkinnedRenderer::shutdown |
( |
| ) |
|
Release all GPU resources owned by this subsystem.
Called from NewRenderer::quit (BEFORE the device is destroyed).
◆ sphereInFrustum()
| bool SkinnedRenderer::sphereInFrustum |
( |
const glm::vec3 & | center, |
|
|
float | radius, |
|
|
const FrustumPlanes & | planes ) |
|
staticprivate |
Test whether a world-space bounding sphere intersects (or lies inside) the view frustum.
Uses the Gribb-Hartmann plane convention of FrustumPlanes (a point is inside a plane when dot(plane.xyz, p) +
plane.w >= 0). Returns false only when the sphere is fully outside at least one plane.
◆ uploadFrame()
| void SkinnedRenderer::uploadFrame |
( |
SDL_GPUCommandBuffer * | cmd, |
|
|
SDL_GPUCopyPass * | copyPass ) |
Upload this frame's palette + instance buffers to the GPU.
- Parameters
-
| cmd | Frame command buffer. |
| copyPass | An open copy pass on cmd. |
Called from NewRenderer::drawFrame BEFORE the main render pass begins so the copy is sequenced ahead of the draws. Uses cycle=true on the transfer-buffer map so we never stall waiting on last frame's GPU read of the SSBO.
◆ chamsIndices_
| std::vector<Uint32> SkinnedRenderer::chamsIndices_ |
|
private |
◆ chamsPipeline_
| SDL_GPUGraphicsPipeline* SkinnedRenderer::chamsPipeline_ = nullptr |
|
private |
Wallhack chams pipeline: flat-red, GREATER depth test, no depth write — draws flagged players where they're occluded by world geometry.
◆ colorFormat_
| SDL_GPUTextureFormat SkinnedRenderer::colorFormat_ {} |
|
private |
Cached formats (from init) needed to build the chams pipeline.
◆ depthPipeline_
| SDL_GPUGraphicsPipeline* SkinnedRenderer::depthPipeline_ = nullptr |
|
private |
Depth-only variant of the skinned pipeline, used to rasterise the rig into the shadow map so the player casts a shadow.
◆ device_
| SDL_GPUDevice* SkinnedRenderer::device_ = nullptr |
|
private |
◆ frameDirty_
| bool SkinnedRenderer::frameDirty_ = false |
|
private |
◆ frameInstances_
◆ framePalette_
| std::vector<glm::mat4> SkinnedRenderer::framePalette_ |
|
private |
◆ frustumCullEnabled_
| bool SkinnedRenderer::frustumCullEnabled_ = false |
|
private |
Optional per-instance frustum cull in setFrame.
◆ instancesSsboInfo_
| SsboInfo SkinnedRenderer::instancesSsboInfo_ |
|
private |
◆ instanceXfer_
| SDL_GPUTransferBuffer* SkinnedRenderer::instanceXfer_ = nullptr |
|
private |
◆ instanceXferCapacityBytes_
| Uint32 SkinnedRenderer::instanceXferCapacityBytes_ = 0 |
|
private |
◆ killcamHighlightIndices_
| std::vector<Uint32> SkinnedRenderer::killcamHighlightIndices_ |
|
private |
◆ killcamHighlightPipeline_
| SDL_GPUGraphicsPipeline* SkinnedRenderer::killcamHighlightPipeline_ = nullptr |
|
private |
Killcam full-body red highlight pipeline.
◆ numJoints_
| int SkinnedRenderer::numJoints_ = 0 |
|
private |
◆ palettesSsboInfo_
| SsboInfo SkinnedRenderer::palettesSsboInfo_ |
|
private |
◆ paletteXfer_
| SDL_GPUTransferBuffer* SkinnedRenderer::paletteXfer_ = nullptr |
|
private |
◆ paletteXferCapacityBytes_
| Uint32 SkinnedRenderer::paletteXferCapacityBytes_ = 0 |
|
private |
◆ pipeline_
| SDL_GPUGraphicsPipeline* SkinnedRenderer::pipeline_ = nullptr |
|
private |
The skinned graphics pipeline.
TODO(graphics): create this in init (or lazily on first frame) with two vertex buffers (ModelVertex, BoneInfluence), two vertex storage buffers (palette, instances), one vertex UBO (view-projection), depth test on, cull mode NONE.
◆ rigBoundingCenter_
| glm::vec3 SkinnedRenderer::rigBoundingCenter_ {0.0f} |
|
private |
◆ rigBoundingRadius_
| float SkinnedRenderer::rigBoundingRadius_ = 0.0f |
|
private |
◆ rigInstalled_
| bool SkinnedRenderer::rigInstalled_ = false |
|
private |
◆ shaderFormat_
| SDL_GPUShaderFormat SkinnedRenderer::shaderFormat_ {} |
|
private |
◆ skinnedMeshes_
| std::vector<SkinnedMesh> SkinnedRenderer::skinnedMeshes_ |
|
private |
◆ visibleInstanceCount_
| Uint32 SkinnedRenderer::visibleInstanceCount_ = 0 |
|
private |
The documentation for this class was generated from the following files: