/* =========================================================================== Copyright (C) 2023-2024 Gian 'myT' Schellenbaum This file is part of Challenge Quake 3 (CNQ3). Challenge Quake 3 is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Challenge Quake 3 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Challenge Quake 3. If not, see . =========================================================================== */ // Gameplay Rendering Pipeline - Nuklear integration #include "srp_local.h" #define MAX_NUKLEAR_VERTEX_COUNT (1024 * 1024) #define MAX_NUKLEAR_INDEX_COUNT ( 256 * 1024) #pragma pack(push, 4) struct VertexRC { float mvp[16]; }; struct PixelRC { uint32_t texture; uint32_t sampler; }; #pragma pack(pop) #pragma pack(push, 1) struct NuklearVertex { float position[2]; float texCoords[2]; byte color[4]; }; #pragma pack(pop) void Nuklear::Init(bool ddhi_, const ShaderByteCode& vs, const ShaderByteCode& ps, TextureFormat::Id rtFormat, HDescriptorTable descTable, RootSignatureDesc* rootSigDesc) { ddhi = ddhi_; descriptorTable = descTable; if(srp.firstInit) { for(int i = 0; i < FrameCount; i++) { FrameResources* fr = &frameResources[i]; BufferDesc vtx("Nuklear index", MAX_NUKLEAR_INDEX_COUNT * sizeof(uint32_t), ResourceStates::IndexBufferBit); vtx.memoryUsage = MemoryUsage::Upload; fr->indexBuffer = CreateBuffer(vtx); BufferDesc idx("Nuklear vertex", MAX_NUKLEAR_VERTEX_COUNT * sizeof(NuklearVertex), ResourceStates::VertexBufferBit); idx.memoryUsage = MemoryUsage::Upload; fr->vertexBuffer = CreateBuffer(idx); } if(!ddhi) { RootSignatureDesc desc = *rootSigDesc; desc.name = "Nuklear"; desc.constants[ShaderStage::Vertex].byteCount = sizeof(VertexRC); desc.constants[ShaderStage::Pixel].byteCount = sizeof(PixelRC); rootSignature = CreateRootSignature(desc); } } { GraphicsPipelineDesc desc("Nuklear", rootSignature); desc.shortLifeTime = true; desc.vertexShader = vs; desc.pixelShader = ps; desc.vertexLayout.bindingStrides[0] = sizeof(NuklearVertex); desc.vertexLayout.AddAttribute(0, ShaderSemantic::Position, DataType::Float32, 2, offsetof(NuklearVertex, position)); desc.vertexLayout.AddAttribute(0, ShaderSemantic::TexCoord, DataType::Float32, 2, offsetof(NuklearVertex, texCoords)); desc.vertexLayout.AddAttribute(0, ShaderSemantic::Color, DataType::UNorm8, 4, offsetof(NuklearVertex, color)); desc.depthStencil.depthComparison = ComparisonFunction::Always; desc.depthStencil.depthStencilFormat = TextureFormat::Depth32_Float; desc.depthStencil.enableDepthTest = false; desc.depthStencil.enableDepthWrites = false; desc.rasterizer.cullMode = CT_TWO_SIDED; desc.AddRenderTarget(GLS_SRCBLEND_SRC_ALPHA | GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA, rtFormat); pipeline = CreateGraphicsPipeline(desc); } } void Nuklear::BeginFrame() { firstVertex = 0; firstIndex = 0; numVertexes = 0; numIndexes = 0; } void Nuklear::Begin(HTexture renderTarget) { if(srp.renderMode == RenderMode::Nuklear) { return; } srp.renderMode = RenderMode::Nuklear; renderPassIndex = srp.BeginRenderPass("Nuklear", 0.75f, 0.75f, 1.0f); FrameResources* const fr = &frameResources[GetFrameIndex()]; const uint32_t vertexStride = sizeof(NuklearVertex); CmdBindRenderTargets(1, &renderTarget, NULL); if(!ddhi) { CmdBindRootSignature(rootSignature); } CmdBindPipeline(pipeline); if(!ddhi) { CmdBindDescriptorTable(rootSignature, descriptorTable); } CmdBindVertexBuffers(1, &fr->vertexBuffer, &vertexStride, NULL); CmdBindIndexBuffer(fr->indexBuffer, IndexType::UInt32, 0); CmdSetViewport(0, 0, glConfig.vidWidth, glConfig.vidHeight); const float L = 0.0f; const float R = glConfig.vidWidth; const float T = 0.0f; const float B = glConfig.vidHeight; const VertexRC vertexRC = { 2.0f / (R - L), 0.0f, 0.0f, 0.0f, 0.0f, 2.0f / (T - B), 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, (R + L) / (L - R), (T + B) / (B - T), 0.5f, 1.0f }; if(ddhi) { CmdSetGraphicsRootConstants(0, sizeof(vertexRC), &vertexRC); } else { CmdSetRootConstants(rootSignature, ShaderStage::Vertex, &vertexRC); } for(int i = 0; i < 4; ++i) { prevScissorRect[i] = 0; } } void Nuklear::End() { srp.EndRenderPass(renderPassIndex); srp.renderMode = RenderMode::None; } void Nuklear::Upload(const nuklearUploadCommand_t& cmd) { firstVertex += numVertexes; firstIndex += numIndexes; numVertexes = cmd.numVertexBytes / sizeof(NuklearVertex); numIndexes = cmd.numIndexBytes / sizeof(uint32_t); if(firstVertex + numVertexes > MAX_NUKLEAR_VERTEX_COUNT || firstIndex + numIndexes > MAX_NUKLEAR_INDEX_COUNT) { return; } FrameResources* const fr = &frameResources[GetFrameIndex()]; NuklearVertex* const vtxDst = (NuklearVertex*)MapBuffer(fr->vertexBuffer) + firstVertex; uint32_t* const idxDst = (uint32_t*)MapBuffer(fr->indexBuffer) + firstIndex; memcpy(vtxDst, cmd.vertexes, cmd.numVertexBytes); memcpy(idxDst, cmd.indexes, cmd.numIndexBytes); UnmapBuffer(fr->vertexBuffer); UnmapBuffer(fr->indexBuffer); } void Nuklear::Draw(const nuklearDrawCommand_t& cmd) { const shader_t* const shader = R_GetShaderByHandle(cmd.shader); if(shader == NULL || shader->numStages < 1 || shader->stages[0] == NULL || !shader->stages[0]->active || shader->stages[0]->bundle.image[0] == NULL) { Q_assert(!"Invalid shader!"); return; } const image_t* const image = R_GetShaderByHandle(cmd.shader)->stages[0]->bundle.image[0]; PixelRC pixelRC = {}; pixelRC.texture = (uint32_t)image->textureIndex; pixelRC.sampler = GetSamplerIndex(image->wrapClampMode, TextureFilter::Linear); if(ddhi) { CmdSetGraphicsRootConstants(sizeof(VertexRC), sizeof(pixelRC), &pixelRC); } else { CmdSetRootConstants(rootSignature, ShaderStage::Pixel, &pixelRC); } if(memcmp(cmd.scissorRect, prevScissorRect, sizeof(prevScissorRect)) != 0) { CmdSetScissor(cmd.scissorRect[0], cmd.scissorRect[1], cmd.scissorRect[2], cmd.scissorRect[3]); memcpy(prevScissorRect, cmd.scissorRect, sizeof(prevScissorRect)); } CmdDrawIndexed(cmd.numIndexes, firstIndex + cmd.firstIndex, firstVertex); }