├── FullDrawTestCase.cpp ├── LICENSE ├── README.md └── ShadowRenderer.cpp /FullDrawTestCase.cpp: -------------------------------------------------------------------------------- 1 | struct SunLight 2 | { 3 | Vector3 direction; 4 | Matrix4x4 viewProj; 5 | 6 | void setDirection(Vector3 _direction) 7 | { 8 | direction = _direction; 9 | auto view = Matrix3x4::view(Vector3(0.0f, 0.0f, 0.0f), direction, Vector3(0.0f, 1.0f, 0.0f)); 10 | auto proj = Matrix4x4::ortho(-80.0f, 80.0f, -80.0f, 80.0f, -80.0f, 80.0f); 11 | viewProj = Matrix4x4::mul(Matrix4x4::from3x4(view), proj); 12 | } 13 | }; 14 | 15 | struct Camera 16 | { 17 | Vector3 position; 18 | 19 | Vector3 direction; 20 | Matrix3x4 view; 21 | Matrix4x4 proj; 22 | Matrix4x4 viewProj; 23 | Matrix3x3 viewInvTranspose; 24 | 25 | void setPositionAspect(Vector3 _position, float aspect) 26 | { 27 | position = _position; 28 | view = Matrix3x4::view(Vector3(0.0f, 0.0f, 0.0f), _position, Vector3(0.0f, 1.0f, 0.0f)); 29 | proj = Matrix4x4::projection(F_PI * 0.5f, aspect, 0.1f, 1000.0f); 30 | viewProj = Matrix4x4::mul(Matrix4x4::from3x4(view), proj); 31 | viewInvTranspose = Matrix3x3::inverse(Matrix3x3::from3x4(view)); 32 | } 33 | }; 34 | 35 | struct SceneObject 36 | { 37 | Vector3 position; 38 | Handle material; 39 | Handle mesh; 40 | }; 41 | 42 | class Meshes 43 | { 44 | public: 45 | static constexpr uint32 NUM_MESHES = 1024; 46 | 47 | Meshes(ResourceManager* rm) 48 | { 49 | auto timerGenerateMesh = ScopedTimer("Meshes init"); 50 | 51 | // Cube mesh 52 | static const uint32 numCubeFaces = 6; 53 | static const uint32 numIndices = numCubeFaces * 2 * 3; 54 | static const uint32 numVertices = numCubeFaces * 4; 55 | 56 | uint16 cubeFaceIndices[2 * 3 * 2] = { 57 | 0, 2, 1, 2, 3, 1, // negative side 58 | 1, 2, 0, 1, 3, 2, // positive side 59 | }; 60 | 61 | Vector3 cubePositions[numVertices]{ 62 | // X- 63 | Vector3(-1.0f, -1.0f, -1.0f), 64 | Vector3(-1.0f, 1.0f, -1.0f), 65 | Vector3(-1.0f , -1.0f, 1.0f), 66 | Vector3(-1.0f, 1.0f, 1.0f), 67 | 68 | // X+ 69 | Vector3(1.0f, -1.0f, -1.0f), 70 | Vector3(1.0f, 1.0f, -1.0f), 71 | Vector3(1.0f , -1.0f, 1.0f), 72 | Vector3(1.0f, 1.0f, 1.0f), 73 | 74 | // Y- 75 | Vector3(-1.0f, -1.0f, -1.0f), 76 | Vector3(-1.0f, -1.0f, 1.0f), 77 | Vector3(1.0f, -1.0f, -1.0f), 78 | Vector3(1.0f, -1.0f, 1.0f), 79 | 80 | // Y+ 81 | Vector3(-1.0f, 1.0f, -1.0f), 82 | Vector3(-1.0f, 1.0f, 1.0f), 83 | Vector3(1.0f, 1.0f, -1.0f), 84 | Vector3(1.0f, 1.0f, 1.0f), 85 | 86 | // Z- 87 | Vector3(-1.0f, -1.0f, -1.0f), 88 | Vector3(1.0f, -1.0f, -1.0f), 89 | Vector3(-1.0f, 1.0f, -1.0f), 90 | Vector3(1.0f, 1.0f, -1.0f), 91 | 92 | // Z+ 93 | Vector3(-1.0f, -1.0f, 1.0f), 94 | Vector3(1.0f, -1.0f, 1.0f), 95 | Vector3(-1.0f, 1.0f, 1.0f), 96 | Vector3(1.0f, 1.0f, 1.0f), 97 | }; 98 | 99 | Vector2 cubeFaceUVs[4]{ 100 | Vector2(0.0f, 0.0f), 101 | Vector2(1.0f, 0.0f), 102 | Vector2(0.0f, 1.0f), 103 | Vector2(1.0f, 1.0f), 104 | }; 105 | 106 | Vector3 cubeNormals[numCubeFaces]{ 107 | Vector3(-1.0f, 0.0f, 0.0f), // X- 108 | Vector3(1.0f, 0.0f, 0.0f), // X+ 109 | Vector3(0.0f, -1.0f, 0.0f), // Y- 110 | Vector3(0.0f, 1.0f, 0.0f), // Y+ 111 | Vector3(0.0f, 0.0f, -1.0f), // Z- 112 | Vector3(0.0f, 0.0f, 1.0f), // Z+ 113 | }; 114 | 115 | Vector3 cubeTangents[numCubeFaces]{ 116 | Vector3(0.0f, -1.0f, 0.0f), // X- 117 | Vector3(0.0f, 1.0f, 0.0f), // X+ 118 | Vector3(0.0f, 0.0f, -1.0f), // Y- 119 | Vector3(0.0f, 0.0f, 1.0f), // Y+ 120 | Vector3(-1.0f, 0.0f, 0.0f), // Z- 121 | Vector3(1.0f, 0.0f, 0.0f), // Z+ 122 | }; 123 | 124 | // Unique index/vertex buffer for each mesh 125 | // TODO/OPTIMIZE: Pack to one buffer instead and use binding .byteOffset 126 | for (uint32 i = 0; i < NUM_MESHES; i++) 127 | { 128 | m_indexBuffers[i] = rm->createBuffer({ .byteSize = sizeof(uint16) * numIndices, .usage = BufferDesc::USAGE_INDEX }); 129 | uint16* indices = (uint16*)rm->getBufferData(m_indexBuffers[i]); 130 | for (uint32 j = 0; j < numIndices; j++) 131 | { 132 | uint32 face = j / (2 * 3); 133 | uint32 index = j % (2 * 3 * 2); 134 | indices[j] = cubeFaceIndices[index] + face * 4; 135 | } 136 | 137 | // Two vertex streams: position + properties. Faster for mobile TBDR GPUs and for shadows 138 | m_vertexBuffersPosition[i] = rm->createBuffer({ .byteSize = sizeof(Vector3) * numVertices, .usage = BufferDesc::USAGE_VERTEX }); 139 | m_vertexBuffersProperties[i] = rm->createBuffer({ .byteSize = sizeof(VertexProperties) * numVertices, .usage = BufferDesc::USAGE_VERTEX }); 140 | Vector3* positions = (Vector3*)rm->getBufferData(m_vertexBuffersPosition[i]); 141 | VertexProperties* properties = (VertexProperties*)rm->getBufferData(m_vertexBuffersProperties[i]); 142 | float randomMeshScale = 0.1f + frandom(1.4f); 143 | for (uint32 v = 0; v < numVertices; v++) 144 | { 145 | positions[v] = cubePositions[v] * randomMeshScale; 146 | properties[v].normal = Vector4toHalf4(Vector4(cubeNormals[v / 4], 0.0f)); 147 | properties[v].tangent = Vector4toHalf4(Vector4(cubeTangents[v / 4], 1.0f)); 148 | properties[v].color = { .r = (uint8)random(255), .g = (uint8)random(255), .b = (uint8)random(255), .a = (uint8)random(255) }; 149 | properties[v].texcoord = Vector2toHalf2(cubeFaceUVs[v % 4]); 150 | } 151 | } 152 | 153 | // Meshes 154 | for (uint32 i = 0; i < NUM_MESHES; i++) 155 | { 156 | m_meshes[i] = rm->createMesh({ 157 | .indexOffset = 0, 158 | .indexCount = numIndices, 159 | .vertexOffset = 0, 160 | .vertexCount = numVertices, 161 | .indexBuffer = m_indexBuffers[i], 162 | .vertexBuffers = {m_vertexBuffersPosition[i], m_vertexBuffersProperties[i], Handle()} }); 163 | } 164 | } 165 | 166 | void destroy(ResourceManager* rm) 167 | { 168 | auto timer = ScopedTimer("Meshes destroy"); 169 | for (uint32 i = 0; i < NUM_MESHES; i++) rm->deleteBuffer(m_indexBuffers[i]); 170 | for (uint32 i = 0; i < NUM_MESHES; i++) rm->deleteBuffer(m_vertexBuffersProperties[i]); 171 | for (uint32 i = 0; i < NUM_MESHES; i++) rm->deleteBuffer(m_vertexBuffersPosition[i]); 172 | for (uint32 i = 0; i < NUM_MESHES; i++) rm->deleteMesh(m_meshes[i]); 173 | } 174 | 175 | Handle getMesh(uint32 index) const { return m_meshes[index]; } 176 | 177 | private: 178 | Handle m_vertexBuffersPosition[NUM_MESHES]; 179 | Handle m_vertexBuffersProperties[NUM_MESHES]; 180 | Handle m_indexBuffers[NUM_MESHES]; 181 | Handle m_meshes[NUM_MESHES]; 182 | }; 183 | 184 | class Materials 185 | { 186 | public: 187 | static constexpr uint32 NUM_MATERIALS = 1024; 188 | 189 | Materials(ResourceManager* rm) 190 | { 191 | // Load textures 192 | auto timerLoadTextures = ScopedTimer("Materials - load textures"); 193 | auto textureAlbedo = RGBA8ImageFile("../data/test/SD/RAW/tiles_albedo.png"); 194 | auto textureNormal = RGBA8ImageFile("../data/test/SD/RAW/tiles_normal.png"); 195 | auto textureProperties = RGBA8ImageFile("../data/test/SD/RAW/tiles_properties.png"); 196 | timerLoadTextures.end(); 197 | 198 | auto timerInitAssets = ScopedTimer("Materials init"); 199 | 200 | // Upload textures to GPU 201 | m_materialTextureAlbedo = createTextureWithInitialData(rm, { .dimensions = Vector3I(textureAlbedo.header->width, textureAlbedo.header->height, 1), .format = FORMAT::RGBA8_UNORM }, textureAlbedo.texels); 202 | m_materialTextureNormal = createTextureWithInitialData(rm, { .dimensions = Vector3I(textureNormal.header->width, textureNormal.header->height, 1), .format = FORMAT::RGBA8_UNORM }, textureNormal.texels); 203 | m_materialTextureProperties = createTextureWithInitialData(rm, { .dimensions = Vector3I(textureProperties.header->width, textureProperties.header->height, 1), .format = FORMAT::RGBA8_UNORM }, textureProperties.texels); 204 | 205 | // Unique randomized uniform buffer for each material 206 | // TODO/OPTIMIZE: Pack to one buffer instead and use binding .byteOffset 207 | for (uint32 i = 0; i < NUM_MATERIALS; i++) 208 | { 209 | m_materialUniforms[i] = rm->createBuffer({ .byteSize = sizeof(MaterialUniforms) }); 210 | 211 | MaterialUniforms* matUniforms = (MaterialUniforms*)rm->getBufferData(m_materialUniforms[i]); 212 | auto color = Vector4(frandom(1.0), frandom(1.0), frandom(1.0), 1.0f); 213 | matUniforms->color = color; 214 | matUniforms->colorAmbient = Vector4(frandom(0.125), frandom(0.125), frandom(0.125), 0.0f); 215 | matUniforms->colorRim = Vector4(frandom(1.0), frandom(1.0), frandom(1.0), frandom(1.0)); 216 | matUniforms->colorShadow = color; 217 | matUniforms->valuesRim = Vector4(0.125f, 0.0f, 1.0f, 0.25f); 218 | matUniforms->valuesSpecular = Vector4(0.0f, 1.0f, 1.0f, 0.0f); 219 | } 220 | 221 | // Material bind group (slot 1) 222 | m_materialBindingsLayout = rm->createBindGroupLayout({ 223 | .textures = { 224 | {.slot = 1}, // Albedo 225 | {.slot = 2}, // Normal 226 | {.slot = 3}, // Properties 227 | }, 228 | .buffers = { 229 | {.slot = 0, .stages = BindGroupLayoutDesc::STAGE_FLAGS::PIXEL} 230 | } }); 231 | 232 | for (uint32 i = 0; i < NUM_MATERIALS; i++) 233 | { 234 | m_materialBindings[i] = rm->createBindGroup({ 235 | .layout = m_materialBindingsLayout, 236 | .textures = { 237 | // TODO: Create more textures and randomize 238 | m_materialTextureAlbedo, 239 | m_materialTextureNormal, 240 | m_materialTextureProperties 241 | }, 242 | .buffers = {{.buffer = m_materialUniforms[i]}} }); 243 | } 244 | } 245 | 246 | void destroy(ResourceManager* rm) 247 | { 248 | auto timer = ScopedTimer("Materials destroy"); 249 | for (uint32 i = 0; i < NUM_MATERIALS; i++) rm->deleteBindGroup(m_materialBindings[i]); 250 | rm->deleteBindGroupLayout(m_materialBindingsLayout); 251 | for (uint32 i = 0; i < NUM_MATERIALS; i++) rm->deleteBuffer(m_materialUniforms[i]); 252 | rm->deleteTexture(m_materialTextureProperties); 253 | rm->deleteTexture(m_materialTextureNormal); 254 | rm->deleteTexture(m_materialTextureAlbedo); 255 | } 256 | 257 | Handle getMaterialBindGroup(uint32 index) const { return m_materialBindings[index]; } 258 | Handle getMaterialBindingsLayout() const { return m_materialBindingsLayout; } 259 | 260 | private: 261 | Handle m_materialTextureAlbedo; 262 | Handle m_materialTextureNormal; 263 | Handle m_materialTextureProperties; 264 | Handle m_materialUniforms[NUM_MATERIALS]; 265 | Handle m_materialBindingsLayout; 266 | Handle m_materialBindings[NUM_MATERIALS]; 267 | }; 268 | 269 | class ShadowRenderer 270 | { 271 | public: 272 | ShadowRenderer(ResourceManager* rm, TempRingUniformBuffer& uniformRingBuffer) : m_uniformRingBuffer(uniformRingBuffer) 273 | // TODO: Use GPU temp allocator instead of pre-allocated uniformRingBuffer. Requires BindGroup update. 274 | { 275 | auto timer = ScopedTimer("ShadowRenderer init"); 276 | 277 | // Render pass 278 | m_renderPassLayout = rm->createRenderPassLayout({ 279 | .depthTargetFormat = FORMAT::D32_FLOAT, 280 | .subpasses = { {.depthTarget = true} } }); 281 | 282 | m_renderPass = rm->createRenderPass({ 283 | .layout = m_renderPassLayout, 284 | .depthTarget = {.nextUsage = TEXTURE_LAYOUT::SAMPLED, .clearZ = 0.0f} // inverse Z for better quality 285 | }); 286 | 287 | // Bindings layouts 288 | m_globalsBindingsLayout = rm->createBindGroupLayout({ 289 | .buffers = { 290 | {.slot = 0} // Global uniforms (camera matrices, etc) 291 | } }); 292 | 293 | m_drawBindingsLayout = ResourceManager::ptr->createBindGroupLayout(BindGroupLayoutDesc{ 294 | .buffers = { 295 | {.slot = 0, .type = BindGroupLayoutDesc::BufferBinding::TYPE::UNIFORM_DYNAMIC_OFFSET, .stages = BindGroupLayoutDesc::STAGE_FLAGS::VERTEX} // Transform matrices 296 | } }); 297 | 298 | // Shader 299 | List shadowShaderVS = ReadFile("../data/pack/gen/shader/hyper/mesh_shadow_vert.spv"); 300 | 301 | m_shader = rm->createShader({ 302 | .VS {.byteCode = shadowShaderVS, .entryFunc = "main"}, 303 | .bindGroups = { 304 | { m_globalsBindingsLayout }, // Globals bind group (0) 305 | { }, // Not used (1) 306 | { }, // Not used (2) 307 | { m_drawBindingsLayout } // Per draw bind group (3) 308 | }, 309 | .graphicsState = { 310 | .depthTest = COMPARE::GREATER_OR_EQUAL, // inverse Z for better quality 311 | .vertexBufferBindings { 312 | { 313 | // Position vertex buffer (0) 314 | .byteStride = 12, .attributes = { 315 | {.byteOffset = 0,.format = FORMAT::RGB32_FLOAT} 316 | } 317 | } 318 | }, 319 | .renderPassLayout = m_renderPassLayout 320 | } }); 321 | 322 | // Shadow map texture 323 | auto shadowDimensions = Vector3I(4096, 4096, 1); 324 | 325 | m_shadowMap = rm->createTexture({ 326 | .dimensions = shadowDimensions, 327 | .mips = 1, 328 | .format = FORMAT::D32_FLOAT, 329 | .usage = TextureDesc::USAGE_DEPTH_STENCIL | TextureDesc::USAGE_SAMPLED, 330 | .sampler = {.compare = COMPARE::LESS_OR_EQUAL} }); 331 | 332 | m_framebuffer = rm->createFramebuffer({ 333 | .dimensions = shadowDimensions, 334 | .renderPassLayout = m_renderPassLayout, 335 | .depthTarget = m_shadowMap }); 336 | 337 | // Uniform buffer 338 | m_globalUniforms = rm->createBuffer({ .byteSize = sizeof(ShadowGlobalUniforms) }); 339 | m_uniforms = (ShadowGlobalUniforms*)rm->getBufferData(m_globalUniforms); 340 | 341 | // Bindings 342 | m_globalBindings = rm->createBindGroup({ 343 | .layout = m_globalsBindingsLayout, 344 | .buffers = {{.buffer = m_globalUniforms}} }); 345 | 346 | m_drawBindings = rm->createBindGroup({ 347 | .layout = m_drawBindingsLayout, 348 | .buffers = {{.buffer = uniformRingBuffer.getBuffer(), .byteSize = (uint32)sizeof(ShadowDrawUniforms)}} }); 349 | 350 | } 351 | 352 | void destroy(ResourceManager* rm) 353 | { 354 | auto timer = ScopedTimer("ShadowRenderer destroy"); 355 | rm->deleteBindGroup(m_drawBindings); 356 | rm->deleteBindGroup(m_globalBindings); 357 | rm->deleteBuffer(m_globalUniforms); 358 | rm->deleteFramebuffer(m_framebuffer); 359 | rm->deleteTexture(m_shadowMap); 360 | rm->deleteShader(m_shader); 361 | rm->deleteBindGroupLayout(m_drawBindingsLayout); 362 | rm->deleteBindGroupLayout(m_globalsBindingsLayout); 363 | rm->deleteRenderPass(m_renderPass); 364 | rm->deleteRenderPassLayout(m_renderPassLayout); 365 | } 366 | 367 | void render(Span sceneObjects, const SunLight& light) 368 | { 369 | // Update uniforms 370 | // TODO/FIXME: Needs to be double buffered! 371 | m_uniforms->viewProj = light.viewProj; 372 | 373 | // Draws 374 | // TODO: Implement culling 375 | CommandBuffer* commandBuffer = Renderer::ptr->beginCommandRecording(COMMAND_BUFFER_TYPE::OFFSCREEN); 376 | RenderPassRenderer* passRenderer = commandBuffer->beginRenderPass(m_renderPass, m_framebuffer); 377 | 378 | List draws((uint32)sceneObjects.size); 379 | for (const SceneObject& sceneObject : sceneObjects) 380 | { 381 | auto alloc = m_uniformRingBuffer.bumpAlloc(); 382 | alloc.ptr->model = Matrix3x4::translate(sceneObject.position); 383 | 384 | draws.insert({ 385 | .shader = m_shader, 386 | .mesh = sceneObject.mesh, 387 | .bindGroup1 = Handle(), 388 | .dynamicBufferOffset0 = alloc.offset }); 389 | } 390 | 391 | DrawArea drawArea{ .bindGroup0 = m_globalBindings, .bindGroupDynamicOffsetBuffers = m_drawBindings, .drawOffset = 0, .drawCount = (uint32)sceneObjects.size }; 392 | passRenderer->drawSubpass(drawArea, draws); 393 | 394 | commandBuffer->endRenderPass(passRenderer); 395 | commandBuffer->submit(); 396 | } 397 | 398 | Handle getShadowMap() const { return m_shadowMap; } 399 | 400 | private: 401 | TempRingUniformBuffer& m_uniformRingBuffer; 402 | ShadowGlobalUniforms* m_uniforms; 403 | 404 | Handle m_renderPassLayout; 405 | Handle m_renderPass; 406 | Handle m_globalsBindingsLayout; 407 | Handle m_drawBindingsLayout; 408 | Handle m_shader; 409 | Handle m_shadowMap; 410 | Handle m_framebuffer; 411 | Handle m_globalUniforms; 412 | Handle m_globalBindings; 413 | Handle m_drawBindings; 414 | }; 415 | 416 | class MainRenderer 417 | { 418 | public: 419 | MainRenderer(ResourceManager* rm, TempRingUniformBuffer& uniformRingBuffer, Handle materialBindingsLayout, Handle sunShadowMap) : 420 | m_uniformRingBuffer(uniformRingBuffer) 421 | // TODO: Use GPU temp allocator instead of pre-allocated uniformRingBuffer. Requires BindGroup update. 422 | { 423 | auto timer = ScopedTimer("MainRenderer init"); 424 | 425 | // Bindings layouts 426 | m_globalsBindingsLayout = rm->createBindGroupLayout({ 427 | .textures = { 428 | {.slot = 1} // Global shadowmap 429 | }, 430 | .buffers = { 431 | {.slot = 0} // Global uniforms (camera matrices, etc) 432 | } }); 433 | 434 | m_drawBindingsLayout = ResourceManager::ptr->createBindGroupLayout(BindGroupLayoutDesc{ 435 | .buffers = { 436 | {.slot = 0, .type = BindGroupLayoutDesc::BufferBinding::TYPE::UNIFORM_DYNAMIC_OFFSET, .stages = BindGroupLayoutDesc::STAGE_FLAGS::VERTEX} // Transform matrices 437 | } }); 438 | 439 | // Shader 440 | List shaderVS = ReadFile("../data/pack/gen/shader/hyper/mesh_simple_vert.spv"); 441 | List shaderPS = ReadFile("../data/pack/gen/shader/hyper/mesh_simple_frag.spv"); 442 | 443 | m_shader = rm->createShader({ 444 | .VS {.byteCode = shaderVS, .entryFunc = "main"}, 445 | .PS {.byteCode = shaderPS, .entryFunc = "main"}, 446 | .bindGroups = { 447 | { m_globalsBindingsLayout }, // Globals bind group (0) 448 | { materialBindingsLayout }, // Material bind group (1) 449 | { }, // Not used (2) 450 | { m_drawBindingsLayout } // Per draw bind group (3) 451 | }, 452 | .graphicsState = { 453 | .depthTest = COMPARE::GREATER_OR_EQUAL, // inverse Z for better quality 454 | .vertexBufferBindings { 455 | { 456 | // Position vertex buffer (0) 457 | .byteStride = 12, .attributes = { 458 | {.byteOffset = 0,.format = FORMAT::RGB32_FLOAT} 459 | } 460 | }, 461 | { 462 | // Secondary vertex buffer: tangent, normal, color, texcoord (1) 463 | .byteStride = 24, .attributes = { 464 | {.byteOffset = 0,.format = FORMAT::RGBA16_FLOAT}, 465 | {.byteOffset = 8,.format = FORMAT::RGBA16_FLOAT}, 466 | {.byteOffset = 16,.format = FORMAT::RGBA8_UNORM}, 467 | {.byteOffset = 20,.format = FORMAT::RG16_FLOAT} 468 | } 469 | }, 470 | }, 471 | .renderPassLayout = rm->getBuiltinResources().mainRenderPassLayout 472 | } }); 473 | 474 | // Uniform buffer 475 | m_globalUniforms = rm->createBuffer({ .byteSize = sizeof(GlobalUniforms) }); 476 | m_uniforms = (GlobalUniforms*)rm->getBufferData(m_globalUniforms); 477 | m_uniforms->valuesFog = Vector4(/*fogEnd*/40.0f, /*decayPerMeter*/0.025f, 0.0f, 0.0f); 478 | m_uniforms->colorFog = Vector4(1.0f, 0.0f, 1.0f, 0.0f); 479 | 480 | // Bindings 481 | m_globalBindings = rm->createBindGroup({ 482 | .layout = m_globalsBindingsLayout, 483 | .textures = {sunShadowMap}, 484 | .buffers = {{.buffer = m_globalUniforms}} }); 485 | 486 | m_drawBindings = rm->createBindGroup({ 487 | .layout = m_drawBindingsLayout, 488 | .buffers = {{.buffer = uniformRingBuffer.getBuffer(), .byteSize = (uint32)sizeof(DrawUniforms)}} }); 489 | } 490 | 491 | void destroy(ResourceManager* rm) 492 | { 493 | auto timer = ScopedTimer("MainRenderer destroy"); 494 | rm->deleteBindGroup(m_drawBindings); 495 | rm->deleteBindGroup(m_globalBindings); 496 | rm->deleteBuffer(m_globalUniforms); 497 | rm->deleteShader(m_shader); 498 | rm->deleteBindGroupLayout(m_drawBindingsLayout); 499 | rm->deleteBindGroupLayout(m_globalsBindingsLayout); 500 | } 501 | 502 | void render(Span sceneObjects, const RenderFrame& frame, const SunLight& light, const Camera& camera) 503 | { 504 | // Update uniforms 505 | // TODO/FIXME: Needs to be double buffered! 506 | m_uniforms->view = camera.view; 507 | m_uniforms->viewProj = camera.viewProj; 508 | m_uniforms->viewInvTranspose = camera.viewInvTranspose; 509 | m_uniforms->lightDir = Vector4(light.direction, 0.0f); 510 | m_uniforms->lightViewProj = light.viewProj; 511 | 512 | // Draws 513 | // TODO: Implement culling 514 | CommandBuffer* commandBuffer = Renderer::ptr->beginCommandRecording(COMMAND_BUFFER_TYPE::MAIN); 515 | RenderPassRenderer* passRenderer = commandBuffer->beginRenderPass(frame.mainRenderPass, frame.framebuffer); 516 | 517 | List draws((uint32)sceneObjects.size); 518 | for (const SceneObject& sceneObject : sceneObjects) 519 | { 520 | auto alloc = m_uniformRingBuffer.bumpAlloc(); 521 | Matrix3x4 mat = Matrix3x4::translate(sceneObject.position); 522 | alloc.ptr->model = mat; 523 | alloc.ptr->modelInvTranspose = Matrix3x3::inverse(Matrix3x3::from3x4(mat)); 524 | 525 | draws.insert({ 526 | .shader = m_shader, // TODO: Add more material shaders! 527 | .mesh = sceneObject.mesh, 528 | .bindGroup1 = sceneObject.material, 529 | .dynamicBufferOffset0 = alloc.offset }); 530 | } 531 | 532 | DrawArea drawArea{ .bindGroup0 = m_globalBindings, .bindGroupDynamicOffsetBuffers = m_drawBindings, .drawOffset = 0, .drawCount = (uint32)sceneObjects.size }; 533 | passRenderer->drawSubpass(drawArea, draws); 534 | 535 | commandBuffer->endRenderPass(passRenderer); 536 | commandBuffer->submit(); 537 | 538 | } 539 | 540 | private: 541 | TempRingUniformBuffer& m_uniformRingBuffer; 542 | GlobalUniforms* m_uniforms; 543 | 544 | Handle m_globalsBindingsLayout; 545 | Handle m_drawBindingsLayout; 546 | Handle m_shader; 547 | Handle m_globalUniforms; 548 | Handle m_globalBindings; 549 | Handle m_drawBindings; 550 | }; 551 | 552 | void TestDrawCallsSimple() 553 | { 554 | ResourceManager* rm = ResourceManager::ptr; 555 | Device* device = Device::ptr; 556 | 557 | GPUAlignment alignment = device->getGPUAlignment(); 558 | Vector2I surfaceDimensions = device->getSurfaceDimensions(); 559 | float aspect = (float)surfaceDimensions.x / (float)surfaceDimensions.y; 560 | 561 | SunLight light; 562 | light.setDirection(Vector3(-0.57735f, 0.57735f, 0.57735f)); 563 | 564 | Camera camera; 565 | camera.setPositionAspect(Vector3(-15.0f, 15.0f, 50.0f), aspect); 566 | 567 | auto meshes = Meshes(rm); 568 | auto materials = Materials(rm); 569 | 570 | // Scene: 10000 random objects 571 | auto timerInitScene = ScopedTimer("Init scene"); 572 | const uint32 objCount = 10000; 573 | List sceneObjects(objCount); 574 | for (uint32 i = 0; i < objCount; i++) 575 | { 576 | sceneObjects.insert({ 577 | .position = Vector3( 578 | frandom(100.0f) - 50.0f, 579 | frandom(100.0f) - 50.0f, 580 | frandom(100.0f) - 50.0f), 581 | .material = materials.getMaterialBindGroup(random(Materials::NUM_MATERIALS)), 582 | .mesh = meshes.getMesh(random(Meshes::NUM_MESHES)) }); 583 | } 584 | timerInitScene.end(); 585 | 586 | // Uniform buffer containing all temp draw data 587 | // TODO/FIXME: Use temp allocator instead. Requires BindGroup update. 588 | auto uniformRingBuffer = TempRingUniformBuffer(rm, 1024 * 1024 * 16, alignment.uniformOffset); 589 | 590 | // Renderers 591 | auto shadowRenderer = ShadowRenderer(rm, uniformRingBuffer); 592 | auto mainRenderer = MainRenderer(rm, uniformRingBuffer, materials.getMaterialBindingsLayout(), shadowRenderer.getShadowMap()); 593 | 594 | // Render 100 frames... 595 | auto timerRenderFrames = ScopedTimer("Render 100 frames"); 596 | for (uint32 frameIndex = 0; frameIndex < 100; frameIndex++) 597 | { 598 | // Animate 599 | auto movementDirection = Vector3(0.0f, 0.0f, 1.0f); 600 | for (uint32 i = 0; i < objCount; i++) 601 | { 602 | sceneObjects[i].position += movementDirection * 0.1f; 603 | } 604 | 605 | float lightTime = (float)frameIndex * 0.001f + 123.71f; 606 | light.setDirection(Vector3(math::cos(lightTime), 0.57735f, -math::sin(lightTime)).getNormal()); 607 | 608 | // Render 609 | RenderFrame frame = Renderer::ptr->beginFrame(); 610 | shadowRenderer.render(sceneObjects, light); 611 | mainRenderer.render(sceneObjects, frame, light, camera); 612 | Renderer::ptr->present(); 613 | } 614 | timerRenderFrames.end(); 615 | 616 | // Cleanup 617 | auto timerCleanup = ScopedTimer("Cleanup"); 618 | mainRenderer.destroy(rm); 619 | shadowRenderer.destroy(rm); 620 | uniformRingBuffer.destroy(rm); 621 | materials.destroy(rm); 622 | meshes.destroy(rm); 623 | timerCleanup.end(); 624 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sebastian Aaltonen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SampleCode 2 | Random sample code 3 | -------------------------------------------------------------------------------- /ShadowRenderer.cpp: -------------------------------------------------------------------------------- 1 | class ShadowRenderer 2 | { 3 | public: 4 | ShadowRenderer(ResourceManager* rm, TempRingUniformBuffer& uniformRingBuffer) : m_uniformRingBuffer(uniformRingBuffer) 5 | // TODO: Use GPU temp allocator instead of pre-allocated uniformRingBuffer. Requires BindGroup update. 6 | { 7 | auto timer = ScopedTimer("ShadowRenderer init"); 8 | 9 | List shadowShaderVS = ReadFile("../data/pack/gen/shader/hyper/mesh_shadow_vert.spv"); 10 | 11 | // Render pass 12 | m_renderPassLayout = rm->createRenderPassLayout({ 13 | .depthTargetFormat = FORMAT::D32_FLOAT, 14 | .subpasses = { {.depthTarget = true} } }); 15 | 16 | m_renderPass = rm->createRenderPass({ 17 | .layout = m_renderPassLayout, 18 | .depthTarget = {.nextUsage = TEXTURE_LAYOUT::SAMPLED, .clearZ = 0.0f} // inverse Z for better quality 19 | }); 20 | 21 | // Bindings layouts 22 | m_globalsBindingsLayout = rm->createBindGroupLayout({ 23 | .buffers = { 24 | {.slot = 0} // Global uniforms (camera matrices, etc) 25 | } }); 26 | 27 | m_drawBindingsLayout = ResourceManager::ptr->createBindGroupLayout(BindGroupLayoutDesc{ 28 | .buffers = { 29 | {.slot = 0, .type = BindGroupLayoutDesc::BufferBinding::TYPE::UNIFORM_DYNAMIC_OFFSET, .stages = BindGroupLayoutDesc::STAGE_FLAGS::VERTEX} // Transform matrices 30 | } }); 31 | 32 | // Shader 33 | m_shader = rm->createShader({ 34 | .VS {.byteCode = shadowShaderVS, .entryFunc = "main"}, 35 | .bindGroups = { 36 | { m_globalsBindingsLayout }, // Globals bind group (0) 37 | { }, // Not used (1) 38 | { }, // Not used (2) 39 | { m_drawBindingsLayout } // Per draw bind group (3) 40 | }, 41 | .graphicsState = { 42 | .depthTest = COMPARE::GREATER_OR_EQUAL, // inverse Z for better quality 43 | .vertexBufferBindings { 44 | { 45 | // Position vertex buffer (0) 46 | .byteStride = 12, .attributes = { 47 | {.byteOffset = 0,.format = FORMAT::RGB32_FLOAT} 48 | } 49 | } 50 | }, 51 | .renderPassLayout = m_renderPassLayout 52 | } }); 53 | 54 | // Shadow map texture 55 | auto shadowDimensions = Vector3I(4096, 4096, 1); 56 | 57 | m_shadowMap = rm->createTexture({ 58 | .dimensions = shadowDimensions, 59 | .mips = 1, 60 | .format = FORMAT::D32_FLOAT, 61 | .usage = TextureDesc::USAGE_DEPTH_STENCIL | TextureDesc::USAGE_SAMPLED, 62 | .sampler = {.compare = COMPARE::LESS_OR_EQUAL} }); 63 | 64 | m_framebuffer = rm->createFramebuffer({ 65 | .dimensions = shadowDimensions, 66 | .renderPassLayout = m_renderPassLayout, 67 | .depthTarget = m_shadowMap }); 68 | 69 | // Uniform buffer 70 | m_globalUniforms = rm->createBuffer({ .byteSize = sizeof(ShadowGlobalUniforms) }); 71 | m_uniforms = (ShadowGlobalUniforms*)rm->getBufferData(m_globalUniforms); 72 | 73 | // Bindings 74 | m_globalBindings = rm->createBindGroup({ 75 | .layout = m_globalsBindingsLayout, 76 | .buffers = {{.buffer = m_globalUniforms}} }); 77 | 78 | m_drawBindings = rm->createBindGroup({ 79 | .layout = m_drawBindingsLayout, 80 | .buffers = {{.buffer = uniformRingBuffer.getBuffer(), .byteSize = (uint32)sizeof(ShadowDrawUniforms)}} }); 81 | 82 | } 83 | 84 | void destroy(ResourceManager* rm) 85 | { 86 | auto timer = ScopedTimer("ShadowRenderer destroy"); 87 | rm->deleteBindGroup(m_drawBindings); 88 | rm->deleteBindGroup(m_globalBindings); 89 | rm->deleteBuffer(m_globalUniforms); 90 | rm->deleteFramebuffer(m_framebuffer); 91 | rm->deleteTexture(m_shadowMap); 92 | rm->deleteShader(m_shader); 93 | rm->deleteBindGroupLayout(m_drawBindingsLayout); 94 | rm->deleteBindGroupLayout(m_globalsBindingsLayout); 95 | rm->deleteRenderPass(m_renderPass); 96 | rm->deleteRenderPassLayout(m_renderPassLayout); 97 | } 98 | 99 | void render(Span sceneObjects, const SunLight& light) 100 | { 101 | // Update uniforms 102 | // TODO/FIXME: Needs to be double buffered! 103 | m_uniforms->viewProj = light.viewProj; 104 | 105 | CommandBuffer* commandBuffer = Renderer::ptr->beginCommandRecording(COMMAND_BUFFER_TYPE::OFFSCREEN); 106 | RenderPassRenderer* passRenderer = commandBuffer->beginRenderPass(m_renderPass, m_framebuffer); 107 | 108 | // Draws 109 | // TODO: Implement culling 110 | List draws((uint32)sceneObjects.size); 111 | for (const SceneObject& sceneObject : sceneObjects) 112 | { 113 | auto alloc = m_uniformRingBuffer.bumpAlloc(); 114 | alloc.ptr->model = Matrix3x4::translate(sceneObject.position); 115 | 116 | draws.insert({ 117 | .shader = m_shader, 118 | .mesh = sceneObject.mesh, 119 | .bindGroup1 = Handle(), 120 | .dynamicBufferOffset0 = alloc.offset }); 121 | } 122 | 123 | DrawArea drawArea{ .bindGroup0 = m_globalBindings, .bindGroupDynamicOffsetBuffers = m_drawBindings, .drawOffset = 0, .drawCount = (uint32)sceneObjects.size }; 124 | passRenderer->drawSubpass(drawArea, draws); 125 | 126 | commandBuffer->endRenderPass(passRenderer); 127 | commandBuffer->submit(); 128 | } 129 | 130 | Handle getShadowMap() const { return m_shadowMap; } 131 | 132 | private: 133 | TempRingUniformBuffer& m_uniformRingBuffer; 134 | ShadowGlobalUniforms* m_uniforms; 135 | 136 | Handle m_renderPassLayout; 137 | Handle m_renderPass; 138 | Handle m_globalsBindingsLayout; 139 | Handle m_drawBindingsLayout; 140 | Handle m_shader; 141 | Handle m_shadowMap; 142 | Handle m_framebuffer; 143 | Handle m_globalUniforms; 144 | Handle m_globalBindings; 145 | Handle m_drawBindings; 146 | }; 147 | --------------------------------------------------------------------------------