// IAEngine: 2D Game Engine by IA // Copyright (C) 2025 IASoft (PVT) LTD (oss@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 . #include #include #include #include namespace ia::iae { Mat4 RDC::s_viewMatrix; Mat4 RDC::s_projectionMatrix; SDL_Window *RDC::s_windowHandle; Handle RDC::s_quadGeometry; RDC_Pipeline *RDC::s_primitiveDrawPipeline; RDC_Pipeline *RDC::s_dynamicSpritePipeline; SDL_GPUSampler *RDC::s_linearClampSampler; SDL_GPUSampler *RDC::s_linearRepeatSampler; Vec2 RDC::s_cameraPosition{}; IVec2 RDC::s_viewportExtent; RDC_Texture *RDC::s_defaultTexture{}; RDC_TextureAtlas *RDC::s_staticSpriteAtlas{}; RDC_TextureAtlas *RDC::s_dynamicSpriteAtlas{}; RDC_HostVisibleBuffer *RDC::s_primitiveInstanceBuffer{}; RDC_HostVisibleBuffer *RDC::s_staticSpriteInstanceBuffer{}; RDC_HostVisibleBuffer *RDC::s_dynamicSpriteInstanceBuffer{}; INT32 RDC::s_spriteInstanceCount{}; RDC_SpriteInstanceData RDC::s_spriteInstances[RDC::MAX_SPRITE_COUNT]; INT32 RDC::s_primitiveInstanceCount{}; GeometryVertex RDC::s_primitiveInstances[RDC::MAX_PRIMITIVE_COUNT]; VOID RDC::Initialize(IN IVec2 viewportExtent, IN SDL_Window *windowHandle, IN BOOL isDebugMode) { EmbeddedResources::Initialize(); s_windowHandle = windowHandle; RDC_Device::Initialize(s_windowHandle, isDebugMode); InitializePipelines(); InitializeSamplers(); InitializeGeometries(); InitializeTextures(); InitializeDrawData(); ResizeScreen(viewportExtent); s_viewMatrix = glm::lookAtLH(glm::vec3{s_cameraPosition, -1.0f}, {s_cameraPosition, 0.0f}, {0.0f, 1.0f, 0.0f}); } VOID RDC::Terminate() { RDC_Device::WaitForIdle(); RDC_Device::DestroyGeometry(s_quadGeometry); SDL_ReleaseGPUSampler(RDC_Device::GetHandle(), s_linearClampSampler); SDL_ReleaseGPUSampler(RDC_Device::GetHandle(), s_linearRepeatSampler); delete s_defaultTexture; delete s_staticSpriteAtlas; delete s_dynamicSpriteAtlas; delete s_primitiveDrawPipeline; delete s_dynamicSpritePipeline; delete s_primitiveInstanceBuffer; delete s_staticSpriteInstanceBuffer; delete s_dynamicSpriteInstanceBuffer; RDC_Device::Terminate(); EmbeddedResources::Terminate(); } VOID RDC::ResizeScreen(IN IVec2 newExtent) { s_viewportExtent = newExtent; s_projectionMatrix = glm::orthoLH(0.0f, (FLOAT32) s_viewportExtent.x, (FLOAT32) s_viewportExtent.y, 0.0f, -1.0f, 1.0f); } VOID RDC::Render(IN SDL_GPURenderPass *renderPass, IN SDL_GPUCommandBuffer *commandBuffer) { s_dynamicSpritePipeline->Bind(renderPass); SDL_PushGPUVertexUniformData(commandBuffer, 0, &s_projectionMatrix, sizeof(Mat4)); SDL_PushGPUVertexUniformData(commandBuffer, 1, &s_viewMatrix, sizeof(Mat4)); SDL_GPUTextureSamplerBinding textureBinding{.texture = s_dynamicSpriteAtlas ? s_dynamicSpriteAtlas->GetTexture()->GetHandle() : s_defaultTexture->GetHandle(), .sampler = s_linearRepeatSampler}; SDL_BindGPUFragmentSamplers(renderPass, 0, &textureBinding, 1); RDC_Device::BindGeometry(renderPass, s_quadGeometry); if (s_spriteInstanceCount) { const auto buffer = s_dynamicSpriteInstanceBuffer->GetHandle(); s_dynamicSpriteInstanceBuffer->CopyFrom(s_spriteInstances, sizeof(RDC_SpriteInstanceData) * s_spriteInstanceCount); SDL_BindGPUVertexStorageBuffers(renderPass, 0, &buffer, 1); SDL_DrawGPUIndexedPrimitives(renderPass, 6, s_spriteInstanceCount, 0, 0, 0); } s_spriteInstanceCount = 0; s_primitiveDrawPipeline->Bind(renderPass); SDL_PushGPUVertexUniformData(commandBuffer, 0, &s_projectionMatrix, sizeof(Mat4)); SDL_PushGPUVertexUniformData(commandBuffer, 1, &s_viewMatrix, sizeof(Mat4)); if (s_primitiveInstanceCount) { s_primitiveInstanceBuffer->CopyFrom(s_primitiveInstances, sizeof(GeometryVertex) * s_primitiveInstanceCount); SDL_GPUBufferBinding bufferBindings[] = {{.buffer = s_primitiveInstanceBuffer->GetHandle(), .offset = 0}}; SDL_BindGPUVertexBuffers(renderPass, 0, &bufferBindings[0], 1); SDL_DrawGPUPrimitives(renderPass, s_primitiveInstanceCount << 1, 1, 0, 0); } s_primitiveInstanceCount = 0; } VOID RDC::RenderToWindow() { STATIC SDL_GPURenderPass *ActiveRenderPass{}; STATIC SDL_GPUCommandBuffer *ActiveCommandBuffer{}; STATIC SDL_GPUColorTargetInfo ActiveColorTargetInfo{.clear_color = SDL_FColor{0.39f, 0.58f, 0.92f, 1.0f}, .load_op = SDL_GPU_LOADOP_CLEAR, .store_op = SDL_GPU_STOREOP_STORE}; if (!(ActiveCommandBuffer = SDL_AcquireGPUCommandBuffer(RDC_Device::GetHandle()))) THROW_UNKNOWN("Failed to acquire SDL GPU command buffer: ", SDL_GetError()); SDL_GPUTexture *swapChainTexture{}; if (!SDL_WaitAndAcquireGPUSwapchainTexture(ActiveCommandBuffer, s_windowHandle, &swapChainTexture, nullptr, nullptr)) THROW_UNKNOWN("Failed to acquire SDL GPU Swapchain texture: ", SDL_GetError()); if (!swapChainTexture) return; ActiveColorTargetInfo.texture = swapChainTexture; ActiveRenderPass = SDL_BeginGPURenderPass(ActiveCommandBuffer, &ActiveColorTargetInfo, 1, nullptr); Render(ActiveRenderPass, ActiveCommandBuffer); SDL_EndGPURenderPass(ActiveRenderPass); SDL_SubmitGPUCommandBuffer(ActiveCommandBuffer); RDC_Device::WaitForIdle(); } VOID RDC::RenderToTexture(IN SDL_GPUTexture *texture) { STATIC SDL_GPURenderPass *ActiveRenderPass{}; STATIC SDL_GPUCommandBuffer *ActiveCommandBuffer{}; STATIC SDL_GPUColorTargetInfo ActiveColorTargetInfo{.clear_color = SDL_FColor{0.39f, 0.58f, 0.92f, 1.0f}, .load_op = SDL_GPU_LOADOP_CLEAR, .store_op = SDL_GPU_STOREOP_STORE}; if (!(ActiveCommandBuffer = SDL_AcquireGPUCommandBuffer(RDC_Device::GetHandle()))) THROW_UNKNOWN("Failed to acquire SDL GPU command buffer: ", SDL_GetError()); ActiveColorTargetInfo.texture = texture; ActiveRenderPass = SDL_BeginGPURenderPass(ActiveCommandBuffer, &ActiveColorTargetInfo, 1, nullptr); Render(ActiveRenderPass, ActiveCommandBuffer); SDL_EndGPURenderPass(ActiveRenderPass); SDL_SubmitGPUCommandBuffer(ActiveCommandBuffer); RDC_Device::WaitForIdle(); } Vec2 RDC::DrawSpriteTopLeft(IN Handle _image, IN INT32 tileIndexX, IN INT32 tileIndexY, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset) { const auto image = (ImageData *) _image; const auto _s = Vec2{scale.x * image->TileWidth, scale.y * image->TileHeight}; Mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3{position.x, position.y, 0}); transform = glm::rotate(transform, rotation, glm::vec3(0.0f, 0.0f, 1.0f)); transform = glm::scale(transform, glm::vec3(_s, 1.0f)); s_spriteInstances[s_spriteInstanceCount++] = { .Transform = transform, .TexCoords = s_dynamicSpriteAtlas ? s_dynamicSpriteAtlas->GetTextureCoordinates( _image, tileIndexX, tileIndexY, flipH, flipV, uvOffset) : Vec4{0.0f, 0.0f, 1.0f, 1.0f}, .Color = {1.0f, 1.0f, 1.0f, 1.0f}}; return _s; } Vec2 RDC::DrawSpriteCentered(IN Handle _image, IN INT32 tileIndexX, IN INT32 tileIndexY, IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset) { const auto image = (ImageData *) _image; const auto _s = Vec2{scale.x * image->TileWidth, scale.y * image->TileHeight}; Mat4 transform = glm::translate(glm::mat4(1.0f), glm::vec3{position.x - _s.x / 2.0f, position.y - _s.y / 2.0f, 0}); transform = glm::rotate(transform, rotation, glm::vec3(0.0f, 0.0f, 1.0f)); transform = glm::scale(transform, glm::vec3(_s, 1.0f)); s_spriteInstances[s_spriteInstanceCount++] = { .Transform = transform, .TexCoords = s_dynamicSpriteAtlas ? s_dynamicSpriteAtlas->GetTextureCoordinates( _image, tileIndexX, tileIndexY, flipH, flipV, uvOffset) : Vec4{0.0f, 0.0f, 1.0f, 1.0f}, .Color = {1.0f, 1.0f, 1.0f, 1.0f}}; return _s; } VOID RDC::DrawLine(IN Vec2 start, IN Vec2 end, IN Vec4 color) { s_primitiveInstances[s_primitiveInstanceCount++] = {start, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {end, color}; } VOID RDC::DrawRect(IN Vec2 start, IN Vec2 end, IN Vec4 color) { s_primitiveInstances[s_primitiveInstanceCount++] = {start, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {Vec2{end.x, start.y}, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {Vec2{end.x, start.y}, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {end, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {end, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {Vec2{start.x, end.y}, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {Vec2{start.x, end.y}, color}; s_primitiveInstances[s_primitiveInstanceCount++] = {start, color}; } } // namespace ia::iae namespace ia::iae { Vec2 RDC::GetCameraPosition() { return s_cameraPosition; } VOID RDC::SetCameraPosition(IN Vec2 position) { if B_LIKELY (s_cameraPosition == position) return; s_cameraPosition = position; s_viewMatrix = glm::lookAtLH(glm::vec3{s_cameraPosition, -1.0f}, {s_cameraPosition, 0.0f}, {0.0f, 1.0f, 0.0f}); } Handle RDC::CreateImage(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileCountX, IN INT32 tileCountY) { const auto pixelDataSize = width * height * 4; const auto image = new ImageData{ .Pixels = new UINT8[pixelDataSize], .Width = width, .Height = height, .TileWidth = width / tileCountX, .TileHeight = height / tileCountY, .TileCountX = tileCountX, .TileCountY = tileCountY, }; ia_memcpy(image->Pixels, rgbaData, pixelDataSize); return (Handle) image; } VOID RDC::DestroyImage(IN Handle _image) { const auto image = (ImageData *) _image; delete[] image->Pixels; delete image; } VOID RDC::CompileTextures(IN CONST Vector &images) { if (!images.size()) return; delete s_dynamicSpriteAtlas; s_dynamicSpriteAtlas = new RDC_TextureAtlas(images); } } // namespace ia::iae namespace ia::iae { VOID RDC::InitializeSamplers() { SDL_GPUSamplerCreateInfo createInfo{.min_filter = SDL_GPU_FILTER_NEAREST, .mag_filter = SDL_GPU_FILTER_NEAREST, .mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR, .address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE, .enable_anisotropy = false}; s_linearClampSampler = SDL_CreateGPUSampler(RDC_Device::GetHandle(), &createInfo); createInfo.min_filter = SDL_GPU_FILTER_NEAREST; createInfo.mag_filter = SDL_GPU_FILTER_NEAREST; createInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR; createInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_REPEAT, createInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT, createInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT, s_linearRepeatSampler = SDL_CreateGPUSampler(RDC_Device::GetHandle(), &createInfo); } VOID RDC::InitializeDrawData() { s_dynamicSpriteInstanceBuffer = new RDC_HostVisibleBuffer(RDC_Buffer::EType::STORAGE, sizeof(s_spriteInstances)); s_primitiveInstanceBuffer = new RDC_HostVisibleBuffer(RDC_Buffer::EType::VERTEX, sizeof(s_primitiveInstances)); } VOID RDC::InitializeTextures() { { // Create Default Texture const auto pixels = new UINT8[100 * 100 * 4]; ia_memset(pixels, 0xFF, 100 * 100 * 4); s_defaultTexture = new RDC_Texture(RDC_Texture::EType::SAMPLED, 100, 100); s_defaultTexture->SetImageData(pixels); delete[] pixels; } } VOID RDC::InitializePipelines() { s_primitiveDrawPipeline = new RDC_Pipeline(RDC_Device::GetSwapchainTextureFormat(), RDC_Pipeline::StageDesc{ .SourceData = EmbeddedResources::GetResource("Shaders/PrimitiveDraw.vert"), .SamplerCount = 0, .UniformBufferCount = 2, .StorageBufferCount = 0, }, RDC_Pipeline::StageDesc{ .SourceData = EmbeddedResources::GetResource("Shaders/PrimitiveDraw.frag"), .SamplerCount = 0, .UniformBufferCount = 0, .StorageBufferCount = 0, }, SDL_GPU_PRIMITIVETYPE_LINELIST, true); s_dynamicSpritePipeline = new RDC_Pipeline(RDC_Device::GetSwapchainTextureFormat(), RDC_Pipeline::StageDesc{ .SourceData = EmbeddedResources::GetResource("Shaders/DynamicSprite.vert"), .SamplerCount = 0, .UniformBufferCount = 2, .StorageBufferCount = 1, }, RDC_Pipeline::StageDesc{ .SourceData = EmbeddedResources::GetResource("Shaders/DynamicSprite.frag"), .SamplerCount = 1, .UniformBufferCount = 0, .StorageBufferCount = 0, }, SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, true); } VOID RDC::InitializeGeometries() { s_quadGeometry = RDC_Device::CreateGeometry( { {glm::vec2{0.0f, 1.0f}, glm::vec4{1.0f, 1.0f, 0.0f, 1.0f}}, {glm::vec2{1.0f, 1.0f}, glm::vec4{1.0f, 1.0f, 1.0f, 1.0f}}, {glm::vec2{1.0f, 0.0f}, glm::vec4{1.0f, 1.0f, 1.0f, 0.0f}}, {glm::vec2{0.0f, 0.0f}, glm::vec4{1.0f, 1.0f, 0.0f, 0.0f}}, }, {0, 1, 2, 2, 3, 0}); } } // namespace ia::iae