From 164594e53b8ebd3a893161a1cb396835c7962235 Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 07:30:40 +0100 Subject: [PATCH 1/9] Client: Add validation for lightmap texture coordinates --- Core/NativeClient/Renderer.lua | 27 +++++++++++++++++++-------- Tests/NativeClient/Renderer.spec.lua | 20 ++++++++++++++++++++ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Core/NativeClient/Renderer.lua b/Core/NativeClient/Renderer.lua index b32bb3c5..dcd79da5 100644 --- a/Core/NativeClient/Renderer.lua +++ b/Core/NativeClient/Renderer.lua @@ -70,9 +70,11 @@ local Renderer = { INVALID_INDEX_BUFFER = "Cannot upload geometry with invalid index buffer", INVALID_COLOR_BUFFER = "Cannot upload geometry with invalid color buffer", INVALID_UV_BUFFER = "Cannot upload geometry with invalid diffuse texture coordinates buffer", + INVALID_LIGHTMAP_UV_BUFFER = "Cannot upload geometry with invalid lightmap texture coordinates buffer", INVALID_NORMAL_BUFFER = "Cannot upload geometry with invalid normal buffer", INCOMPLETE_COLOR_BUFFER = "Cannot upload geometry with missing or incomplete vertex colors", INCOMPLETE_UV_BUFFER = "Cannot upload geometry with missing or incomplete diffuse texture coordinates ", + INCOMPLETE_LIGHTMAP_UV_BUFFER = "Cannot upload geometry with missing or incomplete lightmap texture coordinates ", INVALID_MATERIAL = "Invalid material assigned to mesh", INCOMPLETE_NORMAL_BUFFER = "Cannot upload geometry with missing or incomplete surface normals ", }, @@ -605,17 +607,26 @@ function Renderer:ValidateGeometry(mesh) error(self.errorStrings.INCOMPLETE_NORMAL_BUFFER, 0) end - if not mesh.diffuseTextureCoords then - return - end + if mesh.diffuseTextureCoords then + local diffuseTextureCoordsCount = #mesh.diffuseTextureCoords / 2 + if (diffuseTextureCoordsCount * 2) % 2 ~= 0 then + error(self.errorStrings.INVALID_UV_BUFFER, 0) + end - local diffuseTextureCoordsCount = #mesh.diffuseTextureCoords / 2 - if (diffuseTextureCoordsCount * 2) % 2 ~= 0 then - error(self.errorStrings.INVALID_UV_BUFFER, 0) + if vertexCount ~= diffuseTextureCoordsCount then + error(self.errorStrings.INCOMPLETE_UV_BUFFER, 0) + end end - if vertexCount ~= diffuseTextureCoordsCount then - error(self.errorStrings.INCOMPLETE_UV_BUFFER, 0) + if rawget(mesh, "lightmapTextureCoords") then + local lightmapTextureCoordsCount = #mesh.lightmapTextureCoords / 2 + if (lightmapTextureCoordsCount * 2) % 2 ~= 0 then + error(self.errorStrings.INVALID_LIGHTMAP_UV_BUFFER, 0) + end + + if vertexCount ~= lightmapTextureCoordsCount then + error(self.errorStrings.INCOMPLETE_LIGHTMAP_UV_BUFFER, 0) + end end end diff --git a/Tests/NativeClient/Renderer.spec.lua b/Tests/NativeClient/Renderer.spec.lua index c77fb103..2f3ccee0 100644 --- a/Tests/NativeClient/Renderer.spec.lua +++ b/Tests/NativeClient/Renderer.spec.lua @@ -330,6 +330,26 @@ describe("Renderer", function() assertThrows(uploadIncompleteMeshGeometry, expectedErrorMessage) end) + it("should throw if the geometry contains an insufficient number of lightmap texture coordinates", function() + local mesh = table.copy(planeMesh) + mesh.lightmapTextureCoords = { 1, 2, 3 } + local expectedErrorMessage = Renderer.errorStrings.INVALID_LIGHTMAP_UV_BUFFER + local function uploadIncompleteMeshGeometry() + Renderer:UploadMeshGeometry(mesh) + end + assertThrows(uploadIncompleteMeshGeometry, expectedErrorMessage) + end) + + it("should throw if the geometry contains more vertex positions than lightmap texture coordinates", function() + local mesh = table.copy(planeMesh) + mesh.lightmapTextureCoords = {} + local expectedErrorMessage = Renderer.errorStrings.INCOMPLETE_LIGHTMAP_UV_BUFFER + local function uploadIncompleteMeshGeometry() + Renderer:UploadMeshGeometry(mesh) + end + assertThrows(uploadIncompleteMeshGeometry, expectedErrorMessage) + end) + it("should skip geometry that contains no vertex position", function() local mesh = table.copy(planeMesh) mesh.vertexPositions = {} From 7c204ba2f484bf85616ef4b4cc3c58536d6a52c0 Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 12:37:42 +0100 Subject: [PATCH 2/9] Client: Add support for lightmap UVs to the renderer This doesn't do much on its own, but allows uploading the UVs. --- Core/NativeClient/Renderer.lua | 14 ++++++++++++++ Core/NativeClient/WebGPU/Mesh.lua | 2 +- Tests/NativeClient/Renderer.spec.lua | 25 +++++++++++++++++++++---- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/Core/NativeClient/Renderer.lua b/Core/NativeClient/Renderer.lua index dcd79da5..fb4ed2f1 100644 --- a/Core/NativeClient/Renderer.lua +++ b/Core/NativeClient/Renderer.lua @@ -563,6 +563,19 @@ function Renderer:UploadMeshGeometry(mesh) printf("Uploading geometry: %d surface normals (%s)", normalCount, filesize(normalBufferSize)) local surfaceNormalsBuffer = Buffer:CreateVertexBuffer(self.wgpuDevice, mesh.surfaceNormals) + if rawget(mesh, "lightmapTextureCoords") then + local lightmapTextureCoordsCount = #mesh.lightmapTextureCoords / 2 + local lightmapTextureCoordsBufferSize = #mesh.lightmapTextureCoords * ffi.sizeof("float") + printf( + "Uploading geometry: %d lightmap texture coordinates (%s)", + lightmapTextureCoordsCount, + filesize(lightmapTextureCoordsBufferSize) + ) + local lightmapTextureCoordinatesBuffer = Buffer:CreateVertexBuffer(self.wgpuDevice, mesh.lightmapTextureCoords) + + mesh.lightmapTexCoordsBuffer = lightmapTextureCoordinatesBuffer + end + mesh.vertexBuffer = vertexBuffer mesh.colorBuffer = vertexColorsBuffer mesh.indexBuffer = triangleIndicesBuffer @@ -636,6 +649,7 @@ function Renderer:DestroyMeshGeometry(mesh) Buffer:Destroy(rawget(mesh, "colorBuffer")) Buffer:Destroy(rawget(mesh, "indexBuffer")) Buffer:Destroy(rawget(mesh, "diffuseTexCoordsBuffer")) + Buffer:Destroy(rawget(mesh, "lightmapTexCoordsBuffer")) Buffer:Destroy(rawget(mesh, "surfaceNormalsBuffer")) self.meshes = {} diff --git a/Core/NativeClient/WebGPU/Mesh.lua b/Core/NativeClient/WebGPU/Mesh.lua index 4a9abc6b..2c98b16b 100644 --- a/Core/NativeClient/WebGPU/Mesh.lua +++ b/Core/NativeClient/WebGPU/Mesh.lua @@ -3,7 +3,7 @@ local UnlitMeshMaterial = require("Core.NativeClient.WebGPU.Materials.UnlitMeshM local uuid = require("uuid") local Mesh = { - NUM_BUFFERS_PER_MESH = 5, -- Positions, indices, colors, diffuse UVs, normals + MAX_BUFFER_COUNT_PER_MESH = 6, -- Positions, indices, colors, diffuse UVs, normals, lightmap UVs } function Mesh:Construct(name) diff --git a/Tests/NativeClient/Renderer.spec.lua b/Tests/NativeClient/Renderer.spec.lua index 2f3ccee0..b4e33c02 100644 --- a/Tests/NativeClient/Renderer.spec.lua +++ b/Tests/NativeClient/Renderer.spec.lua @@ -13,6 +13,7 @@ local Texture = require("Core.NativeClient.WebGPU.Texture") local WaterSurfaceMaterial = require("Core.NativeClient.WebGPU.Materials.WaterSurfaceMaterial") local planeMesh = Plane() +planeMesh.lightmapTextureCoords = planeMesh.diffuseTextureCoords local function assertEqualArrayContents(arrayBuffer, arrayTable) for index = 0, #arrayTable - 1, 1 do @@ -172,7 +173,7 @@ describe("Renderer", function() Renderer:DestroyMeshGeometry(planeMesh) - assertEquals(#events, 10) + assertEquals(#events, 12) -- Vertex positions local index = 1 @@ -248,6 +249,21 @@ describe("Renderer", function() assertEquals(ffi.sizeof(events[index].payload.data), expectedBufferSize * ffi.sizeof("float")) assertEquals(events[index].payload.size, expectedBufferSize) assertEqualArrayContents(events[index].payload.data, planeMesh.surfaceNormals) + + -- Lightmap texture coordinates + index = index + 1 + expectedBufferSize = Buffer.GetAlignedSize(#planeMesh.lightmapTextureCoords * ffi.sizeof("float")) + assertEquals(events[index].name, "GPU_BUFFER_CREATE") + assertEquals(events[index].payload.descriptor.usage, Buffer.VERTEX_BUFFER_FLAGS) + assertEquals(tonumber(events[index].payload.descriptor.size), expectedBufferSize) + assertEquals(events[index].payload.descriptor.mappedAtCreation ~= 0, false) + + index = index + 1 + assertEquals(events[index].name, "GPU_BUFFER_WRITE") + assertEquals(events[index].payload.bufferOffset, 0) + assertEquals(ffi.sizeof(events[index].payload.data), expectedBufferSize * ffi.sizeof("float")) + assertEquals(events[index].payload.size, expectedBufferSize) + assertEqualArrayContents(events[index].payload.data, planeMesh.lightmapTextureCoords) end) it("should throw if the geometry contains an insufficient number of vertex positions", function() @@ -389,7 +405,7 @@ describe("Renderer", function() Renderer:DestroyMeshGeometry(planeMesh) local events = etrace.filter() - assertEquals(#events, 5) + assertEquals(#events, 6) -- It's technically possible that the wrong buffers are destroyed here, but eh... assertEquals(events[1].name, "GPU_BUFFER_DESTROY") @@ -397,6 +413,7 @@ describe("Renderer", function() assertEquals(events[3].name, "GPU_BUFFER_DESTROY") assertEquals(events[4].name, "GPU_BUFFER_DESTROY") assertEquals(events[5].name, "GPU_BUFFER_DESTROY") + assertEquals(events[6].name, "GPU_BUFFER_DESTROY") -- Default texture coordinates shouldn't be destroyed (implicit) end) end) @@ -521,8 +538,8 @@ describe("Renderer", function() Renderer:ResetScene() local events = etrace.filter() - assertEquals(#events, #scene.meshes * Mesh.NUM_BUFFERS_PER_MESH) - for index = 1, #scene.meshes * Mesh.NUM_BUFFERS_PER_MESH, 1 do + assertEquals(#events, #scene.meshes * Mesh.MAX_BUFFER_COUNT_PER_MESH) + for index = 1, #scene.meshes * Mesh.MAX_BUFFER_COUNT_PER_MESH, 1 do assertEquals(events[index].name, "GPU_BUFFER_DESTROY") end end) From 68fecf172b06162f6089f07a99bcabef4eb3d902 Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 12:38:00 +0100 Subject: [PATCH 3/9] Client: Add support for lightmap textures to the renderer Again this is useless on its own, but allows materials to implement the actual support for lightmaps if they need it. --- Core/NativeClient/Renderer.lua | 27 ++++++++++++++++--- .../Materials/InvisibleBaseMaterial.lua | 4 +++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Core/NativeClient/Renderer.lua b/Core/NativeClient/Renderer.lua index fb4ed2f1..3a24619c 100644 --- a/Core/NativeClient/Renderer.lua +++ b/Core/NativeClient/Renderer.lua @@ -862,6 +862,7 @@ function Renderer:LoadSceneObjects(scene) textureImages = { textureImage } end + -- Process diffuse texture(s) local diffuseTextures = {} for textureIndex = 1, #textureImages, 1 do local image = textureImages[textureIndex] @@ -876,6 +877,15 @@ function Renderer:LoadSceneObjects(scene) elseif #diffuseTextures > 1 then -- Water material (using texture array) mesh.material:AssignDiffuseTextureArray(diffuseTextures) end + + -- Process lightmap texture + local image = rawget(mesh, "lightmapTextureImage") + if image then + self:DebugDumpTextures(mesh, format("lightmap-texture-%s-in.png", scene.mapID)) + local wgpuTextureHandle = self:CreateTextureFromImage(image.rgbaImageBytes, image.width, image.height) + self:DebugDumpTextures(mesh, format("lightmap-texture-%s-out.png", scene.mapID)) + mesh.material:AssignLightmapTexture(wgpuTextureHandle) + end end if scene.ambientLight then @@ -908,10 +918,6 @@ function Renderer:DebugDumpTextures(mesh, fileName) end local diffuseTextureImage = mesh.diffuseTextureImage - if not diffuseTextureImage then - return - end - C_FileSystem.MakeDirectoryTree("Exports") local pngBytes = C_ImageProcessing.EncodePNG( diffuseTextureImage.rgbaImageBytes, @@ -919,6 +925,19 @@ function Renderer:DebugDumpTextures(mesh, fileName) diffuseTextureImage.height ) C_FileSystem.WriteFile(path.join("Exports", fileName), pngBytes) + + local lightmapTextureImage = rawget(mesh, "lightmapTextureImage") + if not lightmapTextureImage then + return + end + + C_FileSystem.MakeDirectoryTree("Exports") + pngBytes = C_ImageProcessing.EncodePNG( + lightmapTextureImage.rgbaImageBytes, + lightmapTextureImage.width, + lightmapTextureImage.height + ) + C_FileSystem.WriteFile(path.join("Exports", fileName), pngBytes) end function Renderer:ResetScene() diff --git a/Core/NativeClient/WebGPU/Materials/InvisibleBaseMaterial.lua b/Core/NativeClient/WebGPU/Materials/InvisibleBaseMaterial.lua index c13286b3..d2628b68 100644 --- a/Core/NativeClient/WebGPU/Materials/InvisibleBaseMaterial.lua +++ b/Core/NativeClient/WebGPU/Materials/InvisibleBaseMaterial.lua @@ -47,6 +47,10 @@ function InvisibleBaseMaterial:AssignDiffuseTexture(texture, wgpuTexture) self.diffuseTexture = texture end +function InvisibleBaseMaterial:AssignLightmapTexture(texture, wgpuTexture) + error("Lightmap textures aren't yet supported by this material", 0) +end + function InvisibleBaseMaterial:UpdateMaterialPropertiesUniform() -- Should only send if the data has actually changed? (optimize later) self.materialPropertiesUniform.data.diffuseRed = self.diffuseColor.red From 94180e50b3ac8dd5ee219cb57f768d2d2a88444f Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 12:52:49 +0100 Subject: [PATCH 4/9] GND: Add placeholder lightmaps for terrain surfaces These should be replaced with the actual lightmap texture later, and of course they aren't yet used by the shaders (or even supported by the material bindings). --- Core/FileFormats/RagnarokGND.lua | 15 +++++++++++++++ Core/FileFormats/RagnarokMap.lua | 4 ++++ .../WebGPU/Materials/GroundMeshMaterial.lua | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/Core/FileFormats/RagnarokGND.lua b/Core/FileFormats/RagnarokGND.lua index 2068b3c9..c14bfc0f 100644 --- a/Core/FileFormats/RagnarokGND.lua +++ b/Core/FileFormats/RagnarokGND.lua @@ -770,6 +770,21 @@ function RagnarokGND:ComputeFlatFaceNormalRight(gridU, gridV) return rightFaceNormal end +function RagnarokGND:GenerateLightmapTextureImage() + -- Should replace with a power-of-two texture containing the actual lightmap slices + local textureFilePath = path.join("Core", "NativeClient", "Assets", "DebugTexture256.png") + local pngFileContents = C_FileSystem.ReadFile(textureFilePath) + + local rgbaImageBytes, width, height = C_ImageProcessing.DecodeFileContents(pngFileContents) + local placeholderLightmapTexture = { + rgbaImageBytes = rgbaImageBytes, + width = width, + height = height, + } + + return placeholderLightmapTexture +end + ffi.cdef(RagnarokGND.cdefs) return RagnarokGND diff --git a/Core/FileFormats/RagnarokMap.lua b/Core/FileFormats/RagnarokMap.lua index 761eff67..f88ff465 100644 --- a/Core/FileFormats/RagnarokMap.lua +++ b/Core/FileFormats/RagnarokMap.lua @@ -88,6 +88,7 @@ function RagnarokMap:LoadTerrainGeometry(mapID) local gnd = self.gnd local groundMeshSections = gnd:GenerateGroundMeshSections() + local sharedLightmapTextureImage = gnd:GenerateLightmapTextureImage() for sectionID, groundMeshSection in pairs(groundMeshSections) do local texturePath = "texture/" .. gnd.diffuseTexturePaths[sectionID] local normalizedTextureImagePath = RagnarokGRF:DecodeFileName(texturePath) @@ -106,6 +107,9 @@ function RagnarokMap:LoadTerrainGeometry(mapID) width = width, height = height, } + + -- Should only upload once and bind the same texture? + groundMeshSection.lightmapTextureImage = sharedLightmapTextureImage end return groundMeshSections diff --git a/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua b/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua index 1edd690b..5ba36995 100644 --- a/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua +++ b/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua @@ -15,6 +15,10 @@ function GroundMeshMaterial:Construct(...) return self.super.Construct(self, ...) end +function GroundMeshMaterial:AssignLightmapTexture(texture) + self.lightmapTexture = texture +end + class("GroundMeshMaterial", GroundMeshMaterial) extend(GroundMeshMaterial, InvisibleBaseMaterial) From dcf3b5231d3b4a54e45d1ec8c2cf00dd10d067bd Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 13:23:34 +0100 Subject: [PATCH 5/9] GND: Enable generating placeholder lightmap UVs Required to bind the placeholder texture in the render loop. --- Core/FileFormats/RagnarokGND.lua | 26 ++++++++++++++++++++++++++ Tests/FileFormats/RagnarokGND.spec.lua | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/Core/FileFormats/RagnarokGND.lua b/Core/FileFormats/RagnarokGND.lua index c14bfc0f..2661366a 100644 --- a/Core/FileFormats/RagnarokGND.lua +++ b/Core/FileFormats/RagnarokGND.lua @@ -174,6 +174,8 @@ function RagnarokGND:DecodeTexturePaths() local groundMeshSection = Mesh("GroundMeshSection" .. textureIndex) groundMeshSection.material = GroundMeshMaterial("GroundMeshSection" .. textureIndex .. "Material") + -- Should preallocate based on observed sizes here? (same as for the other buffers) + groundMeshSection.lightmapTextureCoords = {} table_insert(self.groundMeshSections, groundMeshSection) end end @@ -451,6 +453,16 @@ function RagnarokGND:GenerateSurfaceGeometry(surfaceConstructionInfo) table_insert(mesh.diffuseTextureCoords, surface.uvs.top_right_u) table_insert(mesh.diffuseTextureCoords, surface.uvs.top_right_v) + local lightmapTextureCoords = self:ComputeLightmapTextureCoords(surface.lightmap_slice_id) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.bottomLeftU) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.bottomLeftV) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.bottomRightU) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.bottomRightV) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.topLeftU) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.topLeftV) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.topRightU) + table_insert(mesh.lightmapTextureCoords, lightmapTextureCoords.topRightV) + if surfaceConstructionInfo.facing == RagnarokGND.SURFACE_DIRECTION_UP then local flatFaceNormalLeft = self:ComputeFlatFaceNormalLeft(gridU, gridV) local flatFaceNormalRight = self:ComputeFlatFaceNormalRight(gridU, gridV) @@ -785,6 +797,20 @@ function RagnarokGND:GenerateLightmapTextureImage() return placeholderLightmapTexture end +function RagnarokGND:ComputeLightmapTextureCoords(lightmapSliceID) + -- Should replace this with the actual lightmap coordinates (to be computed) + return { + bottomLeftU = 0, + bottomLeftV = 0, + bottomRightU = 0, + bottomRightV = 0, + topLeftU = 0, + topLeftV = 0, + topRightU = 0, + topRightV = 0, + } +end + ffi.cdef(RagnarokGND.cdefs) return RagnarokGND diff --git a/Tests/FileFormats/RagnarokGND.spec.lua b/Tests/FileFormats/RagnarokGND.spec.lua index a3f8f907..aebc8ff9 100644 --- a/Tests/FileFormats/RagnarokGND.spec.lua +++ b/Tests/FileFormats/RagnarokGND.spec.lua @@ -566,11 +566,18 @@ describe("RagnarokGND", function() triangleConnections = {}, diffuseTextureCoords = {}, surfaceNormals = {}, + lightmapTextureCoords = {}, }, } local sections = gnd:GenerateGroundMeshSections() assertEquals(#sections, 1) -- Index starts at zero assertEquals(table.count(sections), 1) + + -- Remove this once the actual lightmap UVs are being computed + for index, section in ipairs(sections) do + section.lightmapTextureCoords = nil -- No need to snapshot placeholder UVs + end + local json = require("json") local jsonDump = json.prettier(sections) -- Leaving this here because the snapshot likely needs to be recreated once lightmaps/normals are needed From c3a2cc3c546641ca3eefb05d11de1638b2743b84 Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 13:30:27 +0100 Subject: [PATCH 6/9] Client: Increase the GPU limits to support lightmaps --- Core/NativeClient/WebGPU/GPU.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/NativeClient/WebGPU/GPU.lua b/Core/NativeClient/WebGPU/GPU.lua index 34cf07c8..0ee2354e 100644 --- a/Core/NativeClient/WebGPU/GPU.lua +++ b/Core/NativeClient/WebGPU/GPU.lua @@ -114,9 +114,9 @@ function GPU:RequestLogicalDevice(adapter, options) maxTextureDimension2D = Texture.MAX_TEXTURE_DIMENSION, maxTextureDimension3D = 0, maxTextureArrayLayers = 1, -- For the depth/stencil texture - maxVertexAttributes = 4, -- Vertex positions, vertex colors, diffuse texture UVs, normals - maxVertexBuffers = 4, -- Vertex positions, vertex colors, diffuse texture UVs, normals - maxInterStageShaderComponents = 8, -- #(vec3f color, vec2f diffuseTextureCoords, float alpha), normal(vec3f) + maxVertexAttributes = 5, -- Vertex positions, vertex colors, diffuse UVs, normals, lightmap UVs + maxVertexBuffers = 5, -- Vertex positions, vertex colors, diffuse UVs, normals, lightmap UVs + maxInterStageShaderComponents = 10, -- #(vec3f color, vec2f diffuseTextureCoords, float alpha), normal(vec3f), lightmapUV(vec2f) maxBufferSize = GPU.MAX_BUFFER_SIZE, -- DEFAULT maxVertexBufferArrayStride = 20, -- #(Rml::Vertex) maxBindGroups = 3, -- Camera, material, transforms From 9d22be5565340df0fd2ae1486a1da31a7204e7be Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 13:35:43 +0100 Subject: [PATCH 7/9] Client: Allow binding lightmap textures in the render loop It's a bit sketchy and not-at-all general to use the transform slot, but I don't think it matters since lightmaps are a terrain-only feature. --- Core/NativeClient/Renderer.lua | 7 +++++++ Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua | 2 ++ 2 files changed, 9 insertions(+) diff --git a/Core/NativeClient/Renderer.lua b/Core/NativeClient/Renderer.lua index 3a24619c..c7c08016 100644 --- a/Core/NativeClient/Renderer.lua +++ b/Core/NativeClient/Renderer.lua @@ -429,6 +429,13 @@ function Renderer:DrawMesh(renderPass, mesh) self.dummyTextureMaterial:UpdateMaterialPropertiesUniform() -- Wasteful, should only do it once? end + if rawget(mesh.material, "lightmapTexture") then + -- This binding slot is usually reserved for instance transforms, but they aren't needed for the terrain + RenderPassEncoder:SetBindGroup(renderPass, 2, mesh.material.lightmapTextureBindGroup, 0, nil) + local lightmapTexCoordsBufferSize = #mesh.lightmapTextureCoords * ffi.sizeof("float") or GPU.MAX_VERTEX_COUNT + RenderPassEncoder:SetVertexBuffer(renderPass, 4, mesh.lightmapTexCoordsBuffer, 0, lightmapTexCoordsBufferSize) + end + local instanceCount = 1 local firstVertexIndex = 0 local firstInstanceIndex = 0 diff --git a/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua b/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua index 5ba36995..9a9bbfcc 100644 --- a/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua +++ b/Core/NativeClient/WebGPU/Materials/GroundMeshMaterial.lua @@ -16,6 +16,8 @@ function GroundMeshMaterial:Construct(...) end function GroundMeshMaterial:AssignLightmapTexture(texture) + -- It's probably safe to use the diffuse bind group layout here, at least for now? + self.lightmapTextureBindGroup = self:CreateMaterialPropertiesBindGroup(texture) self.lightmapTexture = texture end From 31fc6a89a142f1f734f4feac650cb77cbc7c2974 Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 13:40:04 +0100 Subject: [PATCH 8/9] Client: Update the terrain shader to sample lightmaps Obviously not the right method for actual lightmaps (placeholder). --- .../WebGPU/Shaders/TerrainGeometryShader.wgsl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Core/NativeClient/WebGPU/Shaders/TerrainGeometryShader.wgsl b/Core/NativeClient/WebGPU/Shaders/TerrainGeometryShader.wgsl index 3c3274fc..de69bdd3 100644 --- a/Core/NativeClient/WebGPU/Shaders/TerrainGeometryShader.wgsl +++ b/Core/NativeClient/WebGPU/Shaders/TerrainGeometryShader.wgsl @@ -3,6 +3,7 @@ struct VertexInput { @location(1) color: vec3f, @location(2) diffuseTextureCoords: vec2f, @location(3) surfaceNormal: vec3f, + @location(4) lightmapTextureCoords: vec2f, }; struct VertexOutput { @@ -10,6 +11,7 @@ struct VertexOutput { @location(0) color: vec3f, @location(1) diffuseTextureCoords: vec2f, @location(2) surfaceNormal: vec3f, + @location(4) lightmapTextureCoords: vec2f, }; // CameraBindGroup: Updated once per frame @@ -40,8 +42,9 @@ struct PerMaterialData { @group(1) @binding(2) var uMaterialInstanceData: PerMaterialData; -// InstanceBindGroup: Updated once per mesh instance -// NYI (only for RML UI widgets) +// InstanceBindGroup: Re-used to store the lightmap texture for this material (hacky; I know...) +@group(2) @binding(0) var lightmapTexture: texture_2d; +@group(2) @binding(1) var lightmapTextureSampler: sampler; const MATH_PI = 3.14159266; const DEBUG_ALPHA_OFFSET = 0.0; // Set to non-zero value (e.g., 0.2) to make transparent background pixels visible @@ -115,6 +118,7 @@ fn vs_main(in: VertexInput) -> VertexOutput { out.color = in.color; out.surfaceNormal = in.surfaceNormal; out.diffuseTextureCoords = in.diffuseTextureCoords; + out.lightmapTextureCoords = in.lightmapTextureCoords; return out; } @@ -124,7 +128,11 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f { let diffuseTextureColor = textureSample(diffuseTexture, diffuseTextureSampler, textureCoords); let normal = normalize(in.surfaceNormal); let sunlightColor = uPerSceneData.directionalLightColor.rgb; - let ambientColor = uPerSceneData.ambientLight.rgb; + let ambientColor = uPerSceneData.ambientLight.rgb; + + let lightmapTexCoords = in.lightmapTextureCoords; + var lightmapTextureColor = textureSample(lightmapTexture, lightmapTextureSampler, lightmapTexCoords); + lightmapTextureColor = vec4f(0.05, 0.05, 0.05, 0.05); // Remove once the real lightmaps are sampled here // Simulated fixed-function pipeline (DirectX7/9) - no specular highlights needed AFAICT? let sunlightRayOrigin = -normalize(uPerSceneData.directionalLightDirection.xyz); @@ -134,7 +142,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f { // Screen blending increases the vibrancy of colors (see https://en.wikipedia.org/wiki/Blend_modes#Screen) let contrastCorrectionColor = clampToUnitRange(ambientColor + sunlightColor - (sunlightColor * ambientColor)); - let fragmentColor = in.color * contrastCorrectionColor * combinedLightContribution * diffuseTextureColor.rgb ; + let fragmentColor = in.color * contrastCorrectionColor * combinedLightContribution * diffuseTextureColor.rgb + lightmapTextureColor.rgb; // Gamma-correction: // WebGPU assumes that the colors output by the fragment shader are given in linear space From 8a31d45b321511c0aa05fc7efb37b2868a28241d Mon Sep 17 00:00:00 2001 From: RDW Date: Tue, 30 Jan 2024 13:48:24 +0100 Subject: [PATCH 9/9] Client: Update the pipeline layouts to support lightmaps --- .../Pipelines/BasicTriangleDrawingPipeline.lua | 14 +++++++++++++- .../WebGPU/Pipelines/GroundMeshDrawingPipeline.lua | 7 ++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Core/NativeClient/WebGPU/Pipelines/BasicTriangleDrawingPipeline.lua b/Core/NativeClient/WebGPU/Pipelines/BasicTriangleDrawingPipeline.lua index 4e3c066c..801cad08 100644 --- a/Core/NativeClient/WebGPU/Pipelines/BasicTriangleDrawingPipeline.lua +++ b/Core/NativeClient/WebGPU/Pipelines/BasicTriangleDrawingPipeline.lua @@ -165,11 +165,23 @@ function BasicTriangleDrawingPipeline:CreateVertexBufferLayout() stepMode = ffi.C.WGPUVertexStepMode_Vertex, }) - return new("WGPUVertexBufferLayout[?]", 4, { + local lightmapTexCoordsLayout = new("WGPUVertexBufferLayout", { + attributeCount = 1, -- UV + attributes = new("WGPUVertexAttribute", { + shaderLocation = 4, -- Pass as 5th argument + format = ffi.C.WGPUVertexFormat_Float32x2, -- Vector2D (float) = UV coords + offset = 0, + }), + arrayStride = 2 * sizeof("float"), -- sizeof(Vector2D) = uv + stepMode = ffi.C.WGPUVertexStepMode_Vertex, + }) + + return new("WGPUVertexBufferLayout[?]", 5, { vertexPositionsLayout, vertexColorsLayout, diffuseTexCoordsLayout, surfaceNormalsLayout, + lightmapTexCoordsLayout, }) end diff --git a/Core/NativeClient/WebGPU/Pipelines/GroundMeshDrawingPipeline.lua b/Core/NativeClient/WebGPU/Pipelines/GroundMeshDrawingPipeline.lua index f4638122..d4a8f83c 100644 --- a/Core/NativeClient/WebGPU/Pipelines/GroundMeshDrawingPipeline.lua +++ b/Core/NativeClient/WebGPU/Pipelines/GroundMeshDrawingPipeline.lua @@ -20,7 +20,7 @@ function GroundMeshDrawingPipeline:Construct(wgpuDeviceHandle, textureFormatID) local sharedShaderModule = self:CreateShaderModule(wgpuDeviceHandle) local renderPipelineDescriptor = new("WGPURenderPipelineDescriptor", { vertex = { - bufferCount = 4, -- Vertex positions, colors, diffuse UVs, normals + bufferCount = 5, -- Vertex positions, colors, diffuse UVs, normals, lightmap UVs module = sharedShaderModule, entryPoint = "vs_main", constantCount = 0, @@ -82,10 +82,11 @@ function GroundMeshDrawingPipeline:Construct(wgpuDeviceHandle, textureFormatID) -- Configure resource layout for the vertex shader local pipelineLayoutDescriptor = new("WGPUPipelineLayoutDescriptor", { - bindGroupLayoutCount = 2, - bindGroupLayouts = new("WGPUBindGroupLayout[?]", 2, { + bindGroupLayoutCount = 3, + bindGroupLayouts = new("WGPUBindGroupLayout[?]", 3, { UniformBuffer.cameraBindGroupLayout, UniformBuffer.materialBindGroupLayout, + UniformBuffer.materialBindGroupLayout, -- Re-used for lightmaps }), })