added Nuklear extensions for UI and CGame

This commit is contained in:
myT 2023-11-29 02:57:28 +01:00
parent 3cea7e0c3d
commit 19aad64607
20 changed files with 30849 additions and 30 deletions

View file

@ -323,6 +323,7 @@ void CL_ShutdownCGame()
cls.keyCatchers &= ~KEYCATCH_CGAME;
cls.cgameStarted = qfalse;
cls.cgameForwardInput = 0;
cls.cgameCharEvents = 0;
CL_MapDownload_Cancel();
Cmd_UnregisterArray( cl_cmds );
if ( !cgvm ) {
@ -356,6 +357,10 @@ static qbool CL_CG_GetValue( char* value, int valueSize, const char* key )
{ "trap_CNQ3_NDP_StartVideo", CG_EXT_NDP_STARTVIDEO },
{ "trap_CNQ3_NDP_StopVideo", CG_EXT_NDP_STOPVIDEO },
{ "trap_CNQ3_R_RenderScene", CG_EXT_R_RENDERSCENE },
{ "trap_CNQ3_NK_CreateFontAtlas", CG_EXT_NK_CREATEFONTATLAS },
{ "trap_CNQ3_NK_Upload", CG_EXT_NK_UPLOAD },
{ "trap_CNQ3_NK_Draw", CG_EXT_NK_DRAW },
{ "trap_CNQ3_SetCharEvents", CG_EXT_SETCHAREVENTS },
// commands
{ "screenshotnc", 1 },
{ "screenshotncJPEG", 1 },
@ -708,6 +713,21 @@ static intptr_t CL_CgameSystemCalls( intptr_t *args )
re.RenderScene( VMA(1), args[2] );
return 0;
case CG_EXT_NK_CREATEFONTATLAS:
return RE_NK_CreateFontAtlas( VM_UI, interopBufferOut, interopBufferOutSize, interopBufferIn, interopBufferInSize );
case CG_EXT_NK_UPLOAD:
RE_UploadNuklear( VMA(1), args[2], VMA(3), args[4] );
return 0;
case CG_EXT_NK_DRAW:
RE_DrawNuklear( args[1], args[2], args[3], VMA(4) );
return 0;
case CG_EXT_SETCHAREVENTS:
cls.cgameCharEvents = (qbool)args[1];
return 0;
default:
Com_Error( ERR_DROP, "Bad cgame system trap: %i", args[0] );
}
@ -737,6 +757,7 @@ void CL_InitCGame()
int t = Sys_Milliseconds();
cls.cgameForwardInput = 0;
cls.cgameCharEvents = 0;
cls.cgameNewDemoPlayer = qfalse;
// put away the console

View file

@ -1247,6 +1247,10 @@ void CL_CharEvent( int key ) {
{
VM_Call( uivm, UI_KEY_EVENT, key | K_CHAR_FLAG, qtrue );
}
else if ( cls.cgameCharEvents && (cls.keyCatchers & KEYCATCH_CGAME) != 0 )
{
VM_Call( cgvm, CG_KEY_EVENT, key | K_CHAR_FLAG, qtrue );
}
else if ( cls.keyCatchers & KEYCATCH_MESSAGE )
{
Field_CharEvent( &chatField, key );

View file

@ -763,6 +763,9 @@ static qbool CL_UI_GetValue( char* value, int valueSize, const char* key )
{ "trap_Cmd_SetHelp", UI_EXT_CMD_SETHELP },
{ "trap_Error2", UI_EXT_ERROR2 },
{ "trap_EnableErrorCallback", UI_EXT_ENABLEERRORCALLBACK },
{ "trap_CNQ3_NK_CreateFontAtlas", UI_EXT_NK_CREATEFONTATLAS },
{ "trap_CNQ3_NK_Upload", UI_EXT_NK_UPLOAD },
{ "trap_CNQ3_NK_Draw", UI_EXT_NK_DRAW },
// commands
{ "screenshotnc", 1 },
{ "screenshotncJPEG", 1 },
@ -1147,6 +1150,17 @@ static intptr_t CL_UISystemCalls( intptr_t* args )
cls.uivmCalls[UIVM_ERROR_CALLBACK] = (int)args[1];
return 0;
case UI_EXT_NK_CREATEFONTATLAS:
return RE_NK_CreateFontAtlas( VM_UI, interopBufferOut, interopBufferOutSize, interopBufferIn, interopBufferInSize );
case UI_EXT_NK_UPLOAD:
RE_UploadNuklear( VMA(1), args[2], VMA(3), args[4] );
return 0;
case UI_EXT_NK_DRAW:
RE_DrawNuklear( args[1], args[2], args[3], VMA(4) );
return 0;
default:
Com_Error( ERR_DROP, "Bad UI system trap: %i", args[0] );
}

View file

@ -290,6 +290,9 @@ typedef struct {
// extension: forward input to cgame regardless of keycatcher state?
int cgameForwardInput; // 1=mouse, 2=keys (note: we don't forward the escape key)
// extension: forward char events to cgame the same way it's done for ui
qbool cgameCharEvents;
int framecount;
int frametime; // msec since last frame

30028
code/nuklear/nuklear.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -202,7 +202,11 @@ typedef enum {
CG_EXT_NDP_READUNTIL,
CG_EXT_NDP_STARTVIDEO,
CG_EXT_NDP_STOPVIDEO,
CG_EXT_R_RENDERSCENE
CG_EXT_R_RENDERSCENE,
CG_EXT_NK_CREATEFONTATLAS,
CG_EXT_NK_UPLOAD,
CG_EXT_NK_DRAW,
CG_EXT_SETCHAREVENTS
} cgameImport_t;

View file

@ -148,7 +148,10 @@ typedef enum {
UI_EXT_CVAR_SETHELP,
UI_EXT_CMD_SETHELP,
UI_EXT_ERROR2,
UI_EXT_ENABLEERRORCALLBACK
UI_EXT_ENABLEERRORCALLBACK,
UI_EXT_NK_CREATEFONTATLAS,
UI_EXT_NK_UPLOAD,
UI_EXT_NK_DRAW
} uiImport_t;

View file

@ -508,6 +508,34 @@ struct ImGUI
bool frameStarted = false;
};
struct Nuklear
{
void Init();
void BeginFrame();
void Begin();
void End();
void Upload(const nuklearUploadCommand_t& cmd);
void Draw(const nuklearDrawCommand_t& cmd);
struct FrameResources
{
HBuffer indexBuffer;
HBuffer vertexBuffer;
};
HRootSignature rootSignature;
HPipeline pipeline;
FrameResources frameResources[FrameCount];
uint32_t renderPassIndex;
int prevScissorRect[4];
// reset every frame
int firstVertex;
int firstIndex;
int numVertexes; // set in Upload
int numIndexes; // set in Upload
};
struct RenderMode
{
enum Id
@ -516,6 +544,7 @@ struct RenderMode
UI,
World,
ImGui,
Nuklear,
Count
};
};
@ -688,6 +717,7 @@ struct GRP : IRenderPipeline
ImGUI imgui;
PostProcess post;
SMAA smaa;
Nuklear nuklear;
bool firstInit = true;
RenderMode::Id renderMode; // necessary for sampler selection, useful for debugging
float frameSeed;

View file

@ -295,6 +295,7 @@ void GRP::Init()
world.Init();
mipMapGen.Init();
imgui.Init();
nuklear.Init();
post.Init();
post.SetToneMapInput(renderTarget);
smaa.Init(); // must be after post
@ -321,6 +322,7 @@ void GRP::BeginFrame()
RHI::BeginFrame();
ui.BeginFrame();
world.BeginFrame();
nuklear.BeginFrame();
const float clearColor[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
const TextureBarrier barrier(renderTarget, ResourceStates::RenderTargetBit);
@ -938,7 +940,18 @@ void GRP::ExecuteRenderCommands(const byte* data, bool readbackRequested)
case RC_END_SCENE:
smaa.Draw(((const endSceneCommand_t*)data)->viewParms);
break;
case RC_BEGIN_NK:
nuklear.Begin();
break;
case RC_END_NK:
nuklear.End();
break;
case RC_NK_UPLOAD:
nuklear.Upload(*(const nuklearUploadCommand_t*)data);
break;
case RC_NK_DRAW:
nuklear.Draw(*(const nuklearDrawCommand_t*)data);
break;
default:
Q_assert(!"Unsupported render command type");
return;

View file

@ -0,0 +1,207 @@
/*
===========================================================================
Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Gameplay Rendering Pipeline - Nuklear integration
#include "grp_local.h"
#include "hlsl/nuklear_vs.h"
#include "hlsl/nuklear_ps.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()
{
if(grp.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);
}
{
RootSignatureDesc desc = grp.rootSignatureDesc;
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 = ShaderByteCode(g_vs);
desc.pixelShader = ShaderByteCode(g_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, grp.renderTargetFormat);
pipeline = CreateGraphicsPipeline(desc);
}
}
void Nuklear::BeginFrame()
{
firstVertex = 0;
firstIndex = 0;
numVertexes = 0;
numIndexes = 0;
}
void Nuklear::Begin()
{
if(grp.renderMode == RenderMode::Nuklear)
{
return;
}
grp.renderMode = RenderMode::Nuklear;
renderPassIndex = grp.BeginRenderPass("Nuklear", 0.75f, 0.75f, 1.0f);
FrameResources* const fr = &frameResources[GetFrameIndex()];
const uint32_t vertexStride = sizeof(NuklearVertex);
CmdBindRenderTargets(1, &grp.renderTarget, NULL);
CmdBindRootSignature(rootSignature);
CmdBindPipeline(pipeline);
CmdBindDescriptorTable(rootSignature, grp.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
};
CmdSetRootConstants(rootSignature, ShaderStage::Vertex, &vertexRC);
for(int i = 0; i < 4; ++i)
{
prevScissorRect[i] = 0;
}
}
void Nuklear::End()
{
grp.EndRenderPass(renderPassIndex);
grp.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);
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);
}

View file

@ -0,0 +1,75 @@
/*
===========================================================================
Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Nuklear integration
struct VOut
{
float4 pos : SV_POSITION;
float4 col : COLOR0;
float2 uv : TEXCOORD0;
};
#if VERTEX_SHADER
cbuffer RootConstants
{
float4x4 projectionMatrix;
};
struct VIn
{
float2 pos : POSITION;
float4 col : COLOR0;
float2 uv : TEXCOORD0;
};
VOut vs(VIn input)
{
VOut output;
output.pos = mul(projectionMatrix, float4(input.pos.xy, 0.0, 1.0));
output.col = input.col;
output.uv = input.uv;
return output;
}
#endif
#if PIXEL_SHADER
cbuffer RootConstants
{
uint textureIndex;
uint samplerIndex;
};
SamplerState samplers[96] : register(s0);
Texture2D textures[4096] : register(t0);
float4 ps(VOut input) : SV_Target
{
return input.col * textures[textureIndex].Sample(samplers[samplerIndex], input.uv);
}
#endif

View file

@ -165,12 +165,31 @@ static void End3D( qbool endFrame = qfalse )
}
static void BeginNK()
{
if ( tr.renderMode != RM_NK ) {
tr.renderMode = RM_NK;
AllocateRenderCommand<beginNuklearCommand_t>( RC_BEGIN_NK );
}
}
static void EndNK( qbool endFrame = qfalse )
{
if ( tr.renderMode == RM_NK ) {
tr.renderMode = RM_NONE;
AllocateRenderCommand<endNuklearCommand_t>( RC_END_NK, endFrame );
}
}
void R_AddDrawSurfCmd( drawSurf_t* drawSurfs, int numDrawSurfs, int numTranspSurfs )
{
if ( !CanAllocateRenderCommand<drawSceneViewCommand_t>() )
return;
End2D();
EndNK();
Begin3D();
drawSceneViewCommand_t* const cmd = AllocateRenderCommand<drawSceneViewCommand_t>(RC_DRAW_SCENE_VIEW);
@ -232,6 +251,7 @@ void RE_StretchPic( float x, float y, float w, float h, float s1, float t1, floa
return;
End3D();
EndNK();
Begin2D();
uiDrawQuadCommand_t* const cmd = AllocateRenderCommand<uiDrawQuadCommand_t>(RC_UI_DRAW_QUAD);
@ -255,6 +275,7 @@ void RE_DrawTriangle( float x0, float y0, float x1, float y1, float x2, float y2
return;
End3D();
EndNK();
Begin2D();
uiDrawTriangleCommand_t* const cmd = AllocateRenderCommand<uiDrawTriangleCommand_t>(RC_UI_DRAW_TRIANGLE);
@ -323,6 +344,7 @@ void RE_EndFrame( qbool render )
End2D( qtrue );
End3D( qtrue );
EndNK( qtrue );
if ( render ) {
AllocateRenderCommand<swapBuffersCommand_t>( RC_SWAP_BUFFERS, qtrue );
@ -347,6 +369,7 @@ void RE_TakeVideoFrame( int width, int height, byte *captureBuffer, byte *encode
End2D();
End3D();
EndNK();
videoFrameCommand_t* const cmd = &backEndData->readbackCommands.videoFrame;
cmd->requested = qtrue;
@ -370,3 +393,46 @@ void R_EndScene( const viewParms_t* viewParms )
cmd->viewParms = *viewParms;
}
void RE_UploadNuklear( void* vertexes, int numVertexBytes, void* indexes, int numIndexBytes )
{
if ( !CanAllocateRenderCommand<nuklearUploadCommand_t>() )
return;
End2D();
End3D();
BeginNK();
Q_assert( tr.renderMode == RM_NK );
nuklearUploadCommand_t* const cmd = AllocateRenderCommand<nuklearUploadCommand_t>( RC_NK_UPLOAD );
Q_assert( cmd );
cmd->vertexes = vertexes;
cmd->numVertexBytes = numVertexBytes;
cmd->indexes = indexes;
cmd->numIndexBytes = numIndexBytes;
}
void RE_DrawNuklear( int firstIndex, int numIndexes, qhandle_t shader, const int* scissorRect )
{
if ( !CanAllocateRenderCommand<nuklearDrawCommand_t>() )
return;
Q_assert( tr.renderMode == RM_NK );
if ( tr.renderMode != RM_NK )
return;
nuklearDrawCommand_t* const cmd = AllocateRenderCommand<nuklearDrawCommand_t>( RC_NK_DRAW );
Q_assert( cmd );
cmd->firstIndex = firstIndex;
cmd->numIndexes = numIndexes;
cmd->shader = shader;
cmd->scissorRect[0] = scissorRect[0];
cmd->scissorRect[1] = scissorRect[1];
cmd->scissorRect[2] = scissorRect[2];
cmd->scissorRect[3] = scissorRect[3];
}

View file

@ -864,7 +864,8 @@ typedef struct {
enum renderMode_t {
RM_NONE,
RM_UI,
RM_3D
RM_3D,
RM_NK
};
@ -1447,6 +1448,8 @@ typedef renderCommandBase_t beginUICommand_t;
typedef renderCommandBase_t endUICommand_t;
typedef renderCommandBase_t begin3DCommand_t;
typedef renderCommandBase_t end3DCommand_t;
typedef renderCommandBase_t beginNuklearCommand_t;
typedef renderCommandBase_t endNuklearCommand_t;
struct uiSetColorCommand_t : renderCommandBase_t {
float color[4];
@ -1490,6 +1493,50 @@ struct endSceneCommand_t : renderCommandBase_t {
uint32_t padding2;
};
struct nuklearUploadCommand_t : renderCommandBase_t {
void* vertexes;
void* indexes;
int numVertexBytes;
int numIndexBytes;
};
struct nuklearDrawCommand_t : renderCommandBase_t {
int scissorRect[4]; // x y w h
int firstIndex;
int numIndexes;
qhandle_t shader;
int padding2;
};
#pragma pack(pop)
#define RENDER_COMMAND_LIST(Cmd) \
Cmd(RC_END_OF_LIST, renderCommandBase_t) \
Cmd(RC_BEGIN_UI, beginUICommand_t) \
Cmd(RC_END_UI, endUICommand_t) \
Cmd(RC_UI_SET_COLOR, uiSetColorCommand_t) \
Cmd(RC_UI_DRAW_QUAD, uiDrawQuadCommand_t) \
Cmd(RC_UI_DRAW_TRIANGLE, uiDrawTriangleCommand_t) \
Cmd(RC_BEGIN_3D, begin3DCommand_t) \
Cmd(RC_END_3D, end3DCommand_t) \
Cmd(RC_DRAW_SCENE_VIEW, drawSceneViewCommand_t) \
Cmd(RC_END_SCENE, endSceneCommand_t) \
Cmd(RC_BEGIN_FRAME, beginFrameCommand_t) \
Cmd(RC_SWAP_BUFFERS, swapBuffersCommand_t) \
Cmd(RC_BEGIN_NK, beginNuklearCommand_t) \
Cmd(RC_END_NK, endNuklearCommand_t) \
Cmd(RC_NK_UPLOAD, nuklearUploadCommand_t) \
Cmd(RC_NK_DRAW, nuklearDrawCommand_t)
#define RC(Enum, Type) Enum,
enum renderCommand_t {
RENDER_COMMAND_LIST(RC)
RC_COUNT
};
#undef RC
extern const int renderCommandSizes[RC_COUNT + 1];
struct readbackCommandBase_t {
qbool requested;
};
@ -1516,32 +1563,6 @@ struct readbackCommands_t {
videoFrameCommand_t videoFrame;
};
#pragma pack(pop)
#define RENDER_COMMAND_LIST(Cmd) \
Cmd(RC_END_OF_LIST, renderCommandBase_t) \
Cmd(RC_BEGIN_UI, beginUICommand_t) \
Cmd(RC_END_UI, endUICommand_t) \
Cmd(RC_UI_SET_COLOR, uiSetColorCommand_t) \
Cmd(RC_UI_DRAW_QUAD, uiDrawQuadCommand_t) \
Cmd(RC_UI_DRAW_TRIANGLE, uiDrawTriangleCommand_t) \
Cmd(RC_BEGIN_3D, begin3DCommand_t) \
Cmd(RC_END_3D, end3DCommand_t) \
Cmd(RC_DRAW_SCENE_VIEW, drawSceneViewCommand_t) \
Cmd(RC_END_SCENE, endSceneCommand_t) \
Cmd(RC_BEGIN_FRAME, beginFrameCommand_t) \
Cmd(RC_SWAP_BUFFERS, swapBuffersCommand_t)
#define RC(Enum, Type) Enum,
enum renderCommand_t {
RENDER_COMMAND_LIST(RC)
RC_COUNT
};
#undef RC
extern const int renderCommandSizes[RC_COUNT + 1];
#define MAX_DLIGHTS 32 // completely arbitrary now :D
#define MAX_REFENTITIES 1023 // can't be increased without changing drawsurf bit packing

View file

@ -0,0 +1,302 @@
/*
===========================================================================
Copyright (C) 2023 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 <https://www.gnu.org/licenses/>.
===========================================================================
*/
// Nuklear integration
#include "tr_local.h"
#define NK_IMPLEMENTATION
#define NK_PRIVATE
#define NK_INCLUDE_DEFAULT_ALLOCATOR
#define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
#define NK_INCLUDE_FONT_BAKING
#include "../nuklear/nuklear.h"
#if defined(offsetof)
#undef offsetof
#endif
#define offsetof(s, m) ((intptr_t)&(((s*)0)->m))
#define membersize(s, m) (sizeof(((s*)0)->m))
struct structMember_t
{
int offset;
int numBytes;
};
#define X(Member) { (int)offsetof(nk_font, Member), (int)membersize(nk_font, Member) }
static const structMember_t s_fontMembers[] =
{
X(fallback_codepoint),
X(handle.height),
X(handle.texture.id),
X(info.ascent),
X(info.descent),
X(info.glyph_count),
X(info.glyph_offset),
X(info.height),
X(scale),
X(texture.id)
};
#undef X
#define X(Member) { (int)offsetof(nk_font_glyph, Member), (int)membersize(nk_font_glyph, Member) }
static const structMember_t s_fontGlyphMembers[] =
{
X(codepoint),
X(h),
X(u0),
X(u1),
X(v0),
X(v1),
X(w),
X(x0),
X(x1),
X(xadvance),
X(y0),
X(y1)
};
#undef X
#define X(Member) { (int)offsetof(struct nk_font_config, Member), (int)membersize(struct nk_font_config, Member) }
static const structMember_t s_fontConfigMembers[] =
{
X(merge_mode),
X(pixel_snap),
X(oversample_h),
X(oversample_v),
X(size),
X(coord_type),
X(fallback_glyph),
X(spacing.x),
X(spacing.y)
};
#undef X
#if defined(_DEBUG)
static void ValidateMembers()
{
for(int i = 0; i < ARRAY_LEN(s_fontMembers); i++)
{
Q_assert(s_fontMembers[i].numBytes == 4);
}
for(int i = 0; i < ARRAY_LEN(s_fontGlyphMembers); i++)
{
Q_assert(s_fontGlyphMembers[i].numBytes == 4);
}
for(int i = 0; i < ARRAY_LEN(s_fontConfigMembers); i++)
{
Q_assert(
s_fontConfigMembers[i].numBytes == 4 ||
s_fontConfigMembers[i].numBytes == 1);
}
}
#endif
qhandle_t RE_NK_CreateFontAtlas(vmIndex_t vmIndex, byte* outBuf, int outSize, const byte* inBuf, int inSize)
{
#if defined(_DEBUG)
ValidateMembers();
#endif
qhandle_t shaderHandle = 0;
void* fontData = NULL;
struct nk_font_atlas atlas;
nk_font_atlas_init_default(&atlas);
nk_font_atlas_begin(&atlas);
struct nk_font_config fontConfigs[16];
struct nk_font* fonts[16];
int numRangesPerFont[16];
nk_rune ranges[(16 + 1) * 2 * ARRAY_LEN(fonts)];
int32_t* in = (int32_t*)inBuf;
const int numFonts = *in++;
if(numFonts <= 0 || numFonts > ARRAY_LEN(fonts))
{
goto clean_up;
}
nk_rune* outRange = ranges;
for(int i = 0; i < numFonts; i++)
{
const int skip = *in++;
const char* const filePath = (const char*)in;
in += skip;
const float height = *(float*)in++;
const int pixelSnap = *in++;
const int overSampleH = *in++;
const int overSampleV = *in++;
const int numRangePairs = *in++;
if(height < 1.0f ||
(pixelSnap != 0 && pixelSnap != 1) ||
overSampleH <= 0 ||
overSampleV <= 0 ||
numRangePairs <= 0)
{
goto clean_up;
}
numRangesPerFont[i] = (numRangePairs + 1) * 2;
const int fontSize = FS_ReadFile(filePath, &fontData);
if(fontSize <= 0 || fontData == NULL)
{
goto clean_up;
}
const nk_rune* const rangeStart = outRange;
for(int j = 0; j < numRangePairs; j++)
{
*outRange++ = *in++;
*outRange++ = *in++;
}
*outRange++ = 0;
*outRange++ = 0;
fontConfigs[i] = nk_font_config(height);
fontConfigs[i].pixel_snap = pixelSnap;
fontConfigs[i].oversample_h = overSampleH;
fontConfigs[i].oversample_v = overSampleV;
fontConfigs[i].range = rangeStart;
fonts[i] = nk_font_atlas_add_from_memory(&atlas, fontData, fontSize, height, &fontConfigs[i]);
FS_FreeFile(fontData);
fontData = NULL;
}
int imgWidth, imgHeight;
byte* const imgData = (byte*)nk_font_atlas_bake(&atlas, &imgWidth, &imgHeight, NK_FONT_ATLAS_RGBA32);
if(imgData == NULL)
{
goto clean_up;
}
const char* const name =
vmIndex == VM_UI ?
"$cpma/ui/nuklear_font_atlas" :
"$cpma/cgame/nuklear_font_atlas";
image_t* const image = R_CreateImage(name, imgData, imgWidth, imgHeight, TF_RGBA8, IMG_NOMIPMAP, TW_CLAMP_TO_EDGE);
if(image == NULL)
{
goto clean_up;
}
shaderHandle = RE_RegisterShaderFromImage(name, image);
nk_font_atlas_end(&atlas, nk_handle_id(shaderHandle), 0);
for(int i = 0; i < numFonts; i++)
{
if(fonts[i]->fallback < atlas.glyphs ||
fonts[i]->fallback >= atlas.glyphs + atlas.glyph_count)
{
goto clean_up;
}
}
int numRanges = 0;
for(int i = 0; i < numFonts; i++)
{
int r = 0;
while(fonts[i]->info.ranges[r++] > 0)
{
numRanges++;
}
}
if(numRanges <= 0 || numRanges % 2 != 0)
{
shaderHandle = 0;
goto clean_up;
}
numRanges += 2;
const int numGlyphs = atlas.glyph_count;
const int numEntries =
3 +
numFonts * (ARRAY_LEN(s_fontMembers) + ARRAY_LEN(s_fontConfigMembers) + 2) +
numGlyphs * ARRAY_LEN(s_fontGlyphMembers) +
numRanges;
if(numEntries * 4 > outSize)
{
shaderHandle = 0;
goto clean_up;
}
int firstRange = 0;
int32_t* out = (int32_t*)outBuf;
*out++ = numFonts;
*out++ = numGlyphs;
*out++ = numRanges;
for(int f = 0; f < numFonts; f++)
{
const nk_font* const font = fonts[f];
const int fallbackGlyph = font->fallback - atlas.glyphs;
*out++ = fallbackGlyph;
*out++ = firstRange;
for(int i = 0; i < ARRAY_LEN(s_fontMembers); i++)
{
const byte* const inField = (const byte*)font + s_fontMembers[i].offset;
*out++ = *(int*)inField;
}
for(int i = 0; i < ARRAY_LEN(s_fontConfigMembers); i++)
{
const byte* const inField = (const byte*)font->config + s_fontConfigMembers[i].offset;
if(s_fontConfigMembers[i].numBytes == 1)
*out++ = (int)(*(char*)inField);
else
*out++ = *(int*)inField;
}
firstRange += numRangesPerFont[f];
}
for(int g = 0; g < numGlyphs; g++)
{
const nk_font_glyph* const glyph = atlas.glyphs + g;
for(int i = 0; i < ARRAY_LEN(s_fontGlyphMembers); i++)
{
const byte* const inField = (const byte*)glyph + s_fontGlyphMembers[i].offset;
*out++ = *(int*)inField;
}
}
for(int i = 0; i < numRanges; i++)
{
*out++ = (int)ranges[i];
}
Q_assert(out == (int*)outBuf + numEntries);
clean_up:
nk_font_atlas_clear(&atlas);
if(fontData != NULL)
{
FS_FreeFile(fontData);
}
return shaderHandle;
}

View file

@ -173,6 +173,13 @@ typedef struct {
void (*ComputeCursorPosition)( int* x, int* y );
} refexport_t;
// CNQ3 currently has no real use for the DLL/SO split even though it might be useful at some point in the future
// to switch between parallel implementations or support live code reload
qhandle_t RE_NK_CreateFontAtlas( vmIndex_t, byte * outBuf, int outSize, const byte* inBuf, int inSize ); // 1 call per VM at most
void RE_UploadNuklear( void* vertexes, int numVertexBytes, void* indexes, int numIndexBytes );
void RE_DrawNuklear( int firstIndex, int numIndexes, qhandle_t shader, const int* scissorRect );
//
// these are the functions imported by the refresh module
//

View file

@ -237,6 +237,7 @@ int main(int /*argc*/, const char** argv)
CompileVSAndPS("post_gamma", "post_gamma.hlsl");
CompileVSAndPS("post_inverse_gamma", "post_inverse_gamma.hlsl");
CompileVSAndPS("imgui", "imgui.hlsl");
CompileVSAndPS("nuklear", "nuklear.hlsl");
CompileVSAndPS("ui", "ui.hlsl");
CompileVSAndPS("depth_pre_pass", "depth_pre_pass.hlsl");
CompileVSAndPS("dynamic_light", "dynamic_light.hlsl");

View file

@ -128,6 +128,7 @@
<ClCompile Include="..\..\code\renderer\grp_imgui.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_mip_gen.cpp" />
<ClCompile Include="..\..\code\renderer\grp_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
<ClCompile Include="..\..\code\renderer\grp_smaa.cpp" />
<ClCompile Include="..\..\code\renderer\grp_ui.cpp" />
@ -148,6 +149,7 @@
<ClCompile Include="..\..\code\renderer\tr_mesh.cpp" />
<ClCompile Include="..\..\code\renderer\tr_model.cpp" />
<ClCompile Include="..\..\code\renderer\tr_noise.cpp" />
<ClCompile Include="..\..\code\renderer\tr_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\tr_renderdoc.cpp" />
<ClCompile Include="..\..\code\renderer\tr_scene.cpp" />
<ClCompile Include="..\..\code\renderer\tr_shade_calc.cpp" />
@ -181,6 +183,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\mip_3.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\nuklear.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\post_gamma.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>

View file

@ -23,6 +23,7 @@
<ClCompile Include="..\..\code\renderer\grp_imgui.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_mip_gen.cpp" />
<ClCompile Include="..\..\code\renderer\grp_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
<ClCompile Include="..\..\code\renderer\grp_smaa.cpp" />
<ClCompile Include="..\..\code\renderer\grp_ui.cpp" />
@ -43,6 +44,7 @@
<ClCompile Include="..\..\code\renderer\tr_mesh.cpp" />
<ClCompile Include="..\..\code\renderer\tr_model.cpp" />
<ClCompile Include="..\..\code\renderer\tr_noise.cpp" />
<ClCompile Include="..\..\code\renderer\tr_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\tr_renderdoc.cpp" />
<ClCompile Include="..\..\code\renderer\tr_scene.cpp" />
<ClCompile Include="..\..\code\renderer\tr_shade_calc.cpp" />
@ -76,6 +78,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\mip_3.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\nuklear.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\post_gamma.hlsl">
<Filter>hlsl</Filter>
</FxCompile>

View file

@ -130,6 +130,7 @@
<ClCompile Include="..\..\code\renderer\grp_imgui.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_mip_gen.cpp" />
<ClCompile Include="..\..\code\renderer\grp_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
<ClCompile Include="..\..\code\renderer\grp_smaa.cpp" />
<ClCompile Include="..\..\code\renderer\grp_ui.cpp" />
@ -150,6 +151,7 @@
<ClCompile Include="..\..\code\renderer\tr_mesh.cpp" />
<ClCompile Include="..\..\code\renderer\tr_model.cpp" />
<ClCompile Include="..\..\code\renderer\tr_noise.cpp" />
<ClCompile Include="..\..\code\renderer\tr_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\tr_renderdoc.cpp" />
<ClCompile Include="..\..\code\renderer\tr_scene.cpp" />
<ClCompile Include="..\..\code\renderer\tr_shade_calc.cpp" />
@ -183,6 +185,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\mip_3.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\nuklear.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\post_gamma.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>

View file

@ -23,6 +23,7 @@
<ClCompile Include="..\..\code\renderer\grp_imgui.cpp" />
<ClCompile Include="..\..\code\renderer\grp_main.cpp" />
<ClCompile Include="..\..\code\renderer\grp_mip_gen.cpp" />
<ClCompile Include="..\..\code\renderer\grp_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\grp_post.cpp" />
<ClCompile Include="..\..\code\renderer\grp_smaa.cpp" />
<ClCompile Include="..\..\code\renderer\grp_ui.cpp" />
@ -43,6 +44,7 @@
<ClCompile Include="..\..\code\renderer\tr_mesh.cpp" />
<ClCompile Include="..\..\code\renderer\tr_model.cpp" />
<ClCompile Include="..\..\code\renderer\tr_noise.cpp" />
<ClCompile Include="..\..\code\renderer\tr_nuklear.cpp" />
<ClCompile Include="..\..\code\renderer\tr_renderdoc.cpp" />
<ClCompile Include="..\..\code\renderer\tr_scene.cpp" />
<ClCompile Include="..\..\code\renderer\tr_shade_calc.cpp" />
@ -76,6 +78,9 @@
<FxCompile Include="..\..\code\renderer\hlsl\mip_3.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\nuklear.hlsl">
<Filter>hlsl</Filter>
</FxCompile>
<FxCompile Include="..\..\code\renderer\hlsl\post_gamma.hlsl">
<Filter>hlsl</Filter>
</FxCompile>