417 lines
16 KiB
C++
417 lines
16 KiB
C++
// IAEngine: 2D Game Engine by IA
|
|
// Copyright (C) 2025 IAS (ias@iasoft.dev)
|
|
//
|
|
// This program 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 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include <IAEngine/IAEngine.hpp>
|
|
#include <IAEngine/Rendering/Camera.hpp>
|
|
#include <IAEngine/Rendering/DebugDraw.hpp>
|
|
#include <IAEngine/Rendering/GPUBuffer.hpp>
|
|
#include <IAEngine/Rendering/GPUTexture.hpp>
|
|
#include <IAEngine/Rendering/Pipeline/PostProcess.hpp>
|
|
#include <IAEngine/Rendering/Pipeline/UnlitMesh.hpp>
|
|
#include <IAEngine/Rendering/Renderer.hpp>
|
|
|
|
#include <SDL3/SDL_gpu.h>
|
|
|
|
#include <backends/imgui_impl_sdl3.h>
|
|
#include <backends/imgui_impl_sdlgpu3.h>
|
|
|
|
namespace ia::iae
|
|
{
|
|
struct Mesh
|
|
{
|
|
INT32 IndexCount{};
|
|
RefPtr<GPUBuffer> IndexBuffer;
|
|
RefPtr<GPUBuffer> VertexBuffer;
|
|
};
|
|
|
|
struct RenderState
|
|
{
|
|
BOOL FlippedH{false};
|
|
BOOL FlippedV{false};
|
|
BOOL CameraRelative{true};
|
|
BOOL ScissorEnabled{false};
|
|
glm::vec2 TextureOffset{0.0f, 0.0f};
|
|
SDL_Rect Scissor{0, 0, 0, 0};
|
|
};
|
|
|
|
Vector<Mesh *> g_meshes{};
|
|
RenderState g_renderState{};
|
|
} // namespace ia::iae
|
|
|
|
namespace ia::iae
|
|
{
|
|
EXTERN SDL_Window *g_windowHandle;
|
|
|
|
INT32 Renderer::s_width{};
|
|
INT32 Renderer::s_height{};
|
|
|
|
SDL_GPUDevice *g_gpuDevice{};
|
|
|
|
ImDrawData *g_imDrawData{};
|
|
|
|
// Render State
|
|
SDL_GPUCommandBuffer *g_cmdBuffer{};
|
|
SDL_GPURenderPass *g_renderPass{};
|
|
SDL_GPUTexture *g_swpChainTexture{};
|
|
SDL_GPUTexture *g_depthBufferTexture{};
|
|
SDL_GPUTexture *g_sceneDrawBufferTexture{};
|
|
SDL_GPUTexture *g_debugDrawBufferTexture{};
|
|
|
|
RefPtr<Pipeline_UnlitMesh> g_pipelineUnlitMesh;
|
|
RefPtr<Pipeline_PostProcess> g_pipelinePostProcess;
|
|
|
|
Camera2D g_camera{};
|
|
|
|
glm::mat4 matProjection{1.0f};
|
|
glm::mat4 matView{1.0f};
|
|
glm::mat4 matModel{1.0f};
|
|
|
|
glm::mat4 matIdentity{1.0f};
|
|
|
|
SDL_Rect g_defaultScissor{};
|
|
|
|
Handle g_meshHandleQuad{};
|
|
|
|
Texture g_whiteFillTexture{};
|
|
Texture g_whiteStrokeTexture{};
|
|
|
|
SDL_GPUColorTargetInfo colorTargetInfo = {0};
|
|
SDL_GPUDepthStencilTargetInfo depthStencilTargetInfo = {0};
|
|
|
|
BOOL Renderer::Initialize()
|
|
{
|
|
SDL_GetWindowSizeInPixels(g_windowHandle, &s_width, &s_height);
|
|
|
|
if (!(g_gpuDevice = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV, Engine::IsDebugMode, nullptr)))
|
|
{
|
|
IAE_LOG_ERROR("Couldn't create SDL3 GPU Device: ", SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
if (!SDL_ClaimWindowForGPUDevice(g_gpuDevice, g_windowHandle))
|
|
{
|
|
IAE_LOG_ERROR("Couldn't initialize SDL3 GPU for the window: ", SDL_GetError());
|
|
return false;
|
|
}
|
|
SDL_SetGPUSwapchainParameters(g_gpuDevice, g_windowHandle, SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
|
|
SDL_GPU_PRESENTMODE_VSYNC);
|
|
|
|
DebugDraw::Initailize();
|
|
|
|
if (!GPUBuffer::InitializeStagingBuffer())
|
|
return false;
|
|
|
|
{
|
|
SDL_GPUTextureCreateInfo createInfo{.type = SDL_GPU_TEXTURETYPE_2D,
|
|
.format = SDL_GetGPUSwapchainTextureFormat(g_gpuDevice, g_windowHandle),
|
|
.usage =
|
|
SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER,
|
|
.width = (UINT32) s_width,
|
|
.height = (UINT32) s_height,
|
|
.layer_count_or_depth = 1,
|
|
.num_levels = 1,
|
|
.sample_count = SDL_GPU_SAMPLECOUNT_1};
|
|
g_sceneDrawBufferTexture = SDL_CreateGPUTexture(g_gpuDevice, &createInfo);
|
|
g_debugDrawBufferTexture = SDL_CreateGPUTexture(g_gpuDevice, &createInfo);
|
|
}
|
|
{
|
|
SDL_GPUTextureCreateInfo createInfo{.type = SDL_GPU_TEXTURETYPE_2D,
|
|
.format = SDL_GPU_TEXTUREFORMAT_D16_UNORM,
|
|
.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
|
.width = (UINT32) s_width,
|
|
.height = (UINT32) s_height,
|
|
.layer_count_or_depth = 1,
|
|
.num_levels = 1,
|
|
.sample_count = SDL_GPU_SAMPLECOUNT_1};
|
|
g_depthBufferTexture = SDL_CreateGPUTexture(g_gpuDevice, &createInfo);
|
|
}
|
|
|
|
GPUTexture::Initialize();
|
|
|
|
g_pipelineUnlitMesh = Pipeline_UnlitMesh::Create();
|
|
g_pipelinePostProcess = Pipeline_PostProcess::Create();
|
|
|
|
matProjection = glm::orthoLH(0.0f, (FLOAT32) s_width, (FLOAT32) s_height, 0.0f, -2097152.0f, 2097152.0f);
|
|
|
|
g_defaultScissor.x = 0;
|
|
g_defaultScissor.y = 0;
|
|
g_defaultScissor.w = s_width;
|
|
g_defaultScissor.h = s_height;
|
|
|
|
{ // Generate default textures
|
|
constexpr auto size = 100;
|
|
constexpr auto strokeWeight = 2;
|
|
const auto data = new UINT32[size * size];
|
|
memset(data, 0, size * size * 4);
|
|
for (SIZE_T i = 0; i < size; i++)
|
|
{
|
|
for (SIZE_T j = 0; j < strokeWeight; j++)
|
|
{
|
|
data[i + (size * j)] = 0xFFFFFFFF;
|
|
data[j + (size * i)] = 0xFFFFFFFF;
|
|
data[i + (size * (size - 1 - j))] = 0xFFFFFFFF;
|
|
data[(size - 1 - j) + (size * i)] = 0xFFFFFFFF;
|
|
}
|
|
}
|
|
g_whiteStrokeTexture = Engine::CreateTexture((PCUINT8) data, size, size);
|
|
for (SIZE_T i = 0; i < size * size; i++)
|
|
data[i] = 0xFFFFFFFF;
|
|
g_whiteFillTexture = Engine::CreateTexture((PCUINT8) data, size, size);
|
|
delete[] data;
|
|
}
|
|
|
|
g_meshHandleQuad = CreateMesh(
|
|
{
|
|
{glm::vec3{0, 1, 0}, glm::vec2{0, 1}, glm::vec4{1.0f, 1.0f, 1.0f, 1.0f}},
|
|
{glm::vec3{1, 1, 0}, glm::vec2{1, 1}, glm::vec4{1.0f, 1.0f, 1.0f, 1.0f}},
|
|
{glm::vec3{1, 0, 0}, glm::vec2{1, 0}, glm::vec4{1.0f, 1.0f, 1.0f, 1.0f}},
|
|
{glm::vec3{0, 0, 0}, glm::vec2{0, 0}, glm::vec4{1.0f, 1.0f, 1.0f, 1.0f}},
|
|
},
|
|
{0, 1, 2, 2, 3, 0});
|
|
|
|
colorTargetInfo.clear_color = SDL_FColor{0.33f, 0.33f, 0.33f, 1.0f};
|
|
colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
|
|
|
|
depthStencilTargetInfo.cycle = true;
|
|
depthStencilTargetInfo.clear_depth = 0;
|
|
depthStencilTargetInfo.clear_stencil = 0;
|
|
depthStencilTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
|
|
depthStencilTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
|
|
depthStencilTargetInfo.stencil_load_op = SDL_GPU_LOADOP_CLEAR;
|
|
depthStencilTargetInfo.stencil_store_op = SDL_GPU_STOREOP_STORE;
|
|
|
|
return true;
|
|
}
|
|
|
|
VOID Renderer::Terminate()
|
|
{
|
|
SDL_WaitForGPUIdle(g_gpuDevice);
|
|
|
|
for (auto &mesh : g_meshes)
|
|
{
|
|
mesh->VertexBuffer.reset();
|
|
mesh->IndexBuffer.reset();
|
|
delete mesh;
|
|
}
|
|
|
|
g_pipelineUnlitMesh.reset();
|
|
g_pipelinePostProcess.reset();
|
|
|
|
GPUTexture::Terminate();
|
|
|
|
GPUBuffer::TerminateStagingBuffer();
|
|
|
|
SDL_ReleaseGPUTexture(g_gpuDevice, g_depthBufferTexture);
|
|
SDL_ReleaseGPUTexture(g_gpuDevice, g_sceneDrawBufferTexture);
|
|
SDL_ReleaseGPUTexture(g_gpuDevice, g_debugDrawBufferTexture);
|
|
|
|
DebugDraw::Terminate();
|
|
|
|
SDL_ReleaseWindowFromGPUDevice(g_gpuDevice, g_windowHandle);
|
|
SDL_DestroyGPUDevice(g_gpuDevice);
|
|
}
|
|
|
|
VOID Renderer::BeginFrame()
|
|
{
|
|
if (!(g_cmdBuffer = SDL_AcquireGPUCommandBuffer(g_gpuDevice)))
|
|
{
|
|
IAE_LOG_ERROR("Couldn't acquire SDL3 GPU command buffer: ", SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
colorTargetInfo.texture = g_sceneDrawBufferTexture;
|
|
depthStencilTargetInfo.texture = g_depthBufferTexture;
|
|
|
|
g_renderPass = SDL_BeginGPURenderPass(g_cmdBuffer, &colorTargetInfo, 1, &depthStencilTargetInfo);
|
|
|
|
g_pipelineUnlitMesh->Bind((Handle) g_renderPass);
|
|
SDL_PushGPUVertexUniformData(g_cmdBuffer, 0, &matProjection, sizeof(matProjection));
|
|
matView = g_camera.GetViewMatrix();
|
|
SDL_PushGPUVertexUniformData(g_cmdBuffer, 1, &matView, sizeof(matView));
|
|
}
|
|
|
|
VOID Renderer::EndFrame()
|
|
{
|
|
SDL_EndGPURenderPass(g_renderPass);
|
|
|
|
ImGui_ImplSDLGPU3_NewFrame();
|
|
ImGui_ImplSDL3_NewFrame();
|
|
ImGui::NewFrame();
|
|
DebugDraw::Draw();
|
|
ImGui::Render();
|
|
g_imDrawData = ImGui::GetDrawData();
|
|
ImGui_ImplSDLGPU3_PrepareDrawData(g_imDrawData, g_cmdBuffer);
|
|
colorTargetInfo.texture = g_debugDrawBufferTexture;
|
|
g_renderPass = SDL_BeginGPURenderPass(g_cmdBuffer, &colorTargetInfo, 1, nullptr);
|
|
ImGui_ImplSDLGPU3_RenderDrawData(g_imDrawData, g_cmdBuffer, g_renderPass);
|
|
SDL_EndGPURenderPass(g_renderPass);
|
|
|
|
if (!SDL_WaitAndAcquireGPUSwapchainTexture(g_cmdBuffer, g_windowHandle, &g_swpChainTexture, (PUINT32) &s_width,
|
|
(PUINT32) &s_height))
|
|
{
|
|
IAE_LOG_ERROR("Couldn't acquire SDL3 GPU Swapchain texture: ", SDL_GetError());
|
|
return;
|
|
}
|
|
|
|
if (!g_swpChainTexture)
|
|
return;
|
|
|
|
colorTargetInfo.texture = g_swpChainTexture;
|
|
|
|
g_renderPass = SDL_BeginGPURenderPass(g_cmdBuffer, &colorTargetInfo, 1, nullptr);
|
|
g_pipelinePostProcess->Bind((Handle) g_renderPass);
|
|
SDL_GPUTextureSamplerBinding textureBindings[2] = {
|
|
{
|
|
.texture = g_sceneDrawBufferTexture,
|
|
.sampler = (SDL_GPUSampler*)GPUTexture::GetDefaultSampler()
|
|
},
|
|
{
|
|
.texture = g_debugDrawBufferTexture,
|
|
.sampler = (SDL_GPUSampler*)GPUTexture::GetDefaultSampler()
|
|
},
|
|
};
|
|
SDL_BindGPUFragmentSamplers(g_renderPass, 0, textureBindings, 2);
|
|
SDL_DrawGPUPrimitives(g_renderPass, 6, 1, 0, 0);
|
|
SDL_EndGPURenderPass(g_renderPass);
|
|
|
|
SDL_SubmitGPUCommandBuffer(g_cmdBuffer);
|
|
}
|
|
|
|
VOID Renderer::SetState_FlippedH(IN BOOL value)
|
|
{
|
|
g_renderState.FlippedH = value;
|
|
}
|
|
|
|
VOID Renderer::SetState_FlippedV(IN BOOL value)
|
|
{
|
|
g_renderState.FlippedV = value;
|
|
}
|
|
|
|
VOID Renderer::SetState_CameraRelative(IN BOOL value)
|
|
{
|
|
g_renderState.CameraRelative = value;
|
|
}
|
|
|
|
VOID Renderer::SetState_ScissorEnabled(IN BOOL value)
|
|
{
|
|
g_renderState.ScissorEnabled = value;
|
|
}
|
|
|
|
VOID Renderer::SetState_Scissor(IN CONST glm::vec4 ®ion)
|
|
{
|
|
g_renderState.Scissor.x = region.x;
|
|
g_renderState.Scissor.y = region.y;
|
|
g_renderState.Scissor.w = region.z;
|
|
g_renderState.Scissor.h = region.w;
|
|
}
|
|
|
|
VOID Renderer::SetState_TextureOffset(IN FLOAT32 u, IN FLOAT32 v)
|
|
{
|
|
g_renderState.TextureOffset = {u, v};
|
|
}
|
|
|
|
Camera2D *Renderer::GetCamera()
|
|
{
|
|
return &g_camera;
|
|
}
|
|
} // namespace ia::iae
|
|
|
|
namespace ia::iae
|
|
{
|
|
Handle Renderer::GetMesh_Quad()
|
|
{
|
|
return g_meshHandleQuad;
|
|
}
|
|
|
|
Handle Renderer::CreateMesh(IN CONST Vector<MeshVertex> &vertices, IN CONST Vector<INT32> &indices)
|
|
{
|
|
const auto mesh = CreateUnmanagedMesh(vertices, indices);
|
|
g_meshes.pushBack((Mesh *) mesh);
|
|
return mesh;
|
|
}
|
|
|
|
Handle Renderer::CreateUnmanagedMesh(IN CONST Vector<MeshVertex> &vertices, IN CONST Vector<INT32> &indices)
|
|
{
|
|
const auto mesh = new Mesh();
|
|
mesh->VertexBuffer = GPUBuffer::Create(GPUBuffer::Usage::VERTEX, vertices.data(),
|
|
static_cast<UINT32>(vertices.size() * sizeof(vertices[0])));
|
|
mesh->IndexBuffer = GPUBuffer::Create(GPUBuffer::Usage::INDEX, indices.data(),
|
|
static_cast<UINT32>(indices.size() * sizeof(indices[0])));
|
|
mesh->IndexCount = static_cast<UINT32>(indices.size());
|
|
return (Handle) mesh;
|
|
}
|
|
|
|
VOID Renderer::DestroyUnmanagedMesh(IN Handle handle)
|
|
{
|
|
delete ((Mesh *) handle);
|
|
}
|
|
|
|
VOID Renderer::Draw(IN Handle meshHandle, IN Handle textureHandle, IN CONST glm::vec2 &position,
|
|
IN CONST glm::vec2 &scale, IN FLOAT32 rotation, IN UINT8 layerIndex, IN INT16 sortIndex,
|
|
IN CONST glm::vec4 &colorOverlay)
|
|
{
|
|
IA_ASSERT(sortIndex <= 0x1FFF);
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
STATIC struct
|
|
{
|
|
INT32 FlippedH{false};
|
|
INT32 FlippedV{false};
|
|
glm::vec2 TextureOffset{0.0f, 0.0f};
|
|
glm::vec4 ColorOverlay{1.0f, 1.0f, 1.0f, 1.0f};
|
|
} s_fragmentUniform{};
|
|
|
|
#pragma pack(pop)
|
|
|
|
if (g_renderState.CameraRelative)
|
|
SDL_PushGPUVertexUniformData(g_cmdBuffer, 1, &matView, sizeof(matView));
|
|
else
|
|
SDL_PushGPUVertexUniformData(g_cmdBuffer, 1, &matIdentity, sizeof(matIdentity));
|
|
if (Engine::GetActiveScene()->YSortingEnabled())
|
|
sortIndex += static_cast<INT16>(position.y);
|
|
matModel =
|
|
glm::translate(glm::mat4(1.0f), glm::vec3{position.x, position.y,
|
|
static_cast<FLOAT32>((layerIndex << 13) | (sortIndex & 0x1FFF))});
|
|
matModel = glm::rotate(matModel, rotation, glm::vec3(0.0f, 0.0f, 1.0f));
|
|
matModel = glm::scale(matModel, glm::vec3{scale.x, scale.y, 1.0f});
|
|
SDL_PushGPUVertexUniformData(g_cmdBuffer, 2, &matModel, sizeof(matModel));
|
|
|
|
s_fragmentUniform.ColorOverlay = colorOverlay;
|
|
s_fragmentUniform.FlippedH = g_renderState.FlippedH;
|
|
s_fragmentUniform.FlippedV = g_renderState.FlippedV;
|
|
s_fragmentUniform.TextureOffset = g_renderState.TextureOffset;
|
|
SDL_GPUTextureSamplerBinding textureBinding{
|
|
.texture = (SDL_GPUTexture *) (textureHandle ? textureHandle : g_whiteFillTexture.GetHandle()),
|
|
.sampler = (SDL_GPUSampler *) GPUTexture::GetDefaultSampler()};
|
|
SDL_BindGPUFragmentSamplers(g_renderPass, 0, &textureBinding, 1);
|
|
SDL_PushGPUFragmentUniformData(g_cmdBuffer, 0, &s_fragmentUniform, sizeof(s_fragmentUniform));
|
|
|
|
if (g_renderState.ScissorEnabled)
|
|
SDL_SetGPUScissor(g_renderPass, &g_renderState.Scissor);
|
|
else
|
|
SDL_SetGPUScissor(g_renderPass, &g_defaultScissor);
|
|
|
|
const auto mesh = (Mesh *) meshHandle;
|
|
SDL_GPUBufferBinding bufferBindings[] = {
|
|
{.buffer = (SDL_GPUBuffer *) mesh->VertexBuffer->GetHandle(), .offset = 0},
|
|
{.buffer = (SDL_GPUBuffer *) mesh->IndexBuffer->GetHandle(), .offset = 0}};
|
|
SDL_BindGPUVertexBuffers(g_renderPass, 0, bufferBindings, 1);
|
|
SDL_BindGPUIndexBuffer(g_renderPass, &bufferBindings[1], SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
|
SDL_DrawGPUIndexedPrimitives(g_renderPass, mesh->IndexCount, 1, 0, 0, 0);
|
|
}
|
|
} // namespace ia::iae
|