RenderCore

This commit is contained in:
Isuru Samarathunga
2025-11-04 22:18:48 +05:30
parent ca75777f19
commit 2de8634184
38 changed files with 1465 additions and 1100 deletions

3
.gitmodules vendored
View File

@ -5,9 +5,6 @@
[submodule "Vendor/SDL_mixer"] [submodule "Vendor/SDL_mixer"]
path = Vendor/SDL_mixer path = Vendor/SDL_mixer
url = https://github.com/libsdl-org/SDL_mixer url = https://github.com/libsdl-org/SDL_mixer
[submodule "Vendor/freetype"]
path = Vendor/freetype
url = https://github.com/freetype/freetype
[submodule "Vendor/zlib"] [submodule "Vendor/zlib"]
path = Vendor/zlib path = Vendor/zlib
url = https://github.com/madler/zlib url = https://github.com/madler/zlib

View File

@ -1,26 +0,0 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 inTexCoord;
layout(location = 1) in vec4 inVertexColor;
layout(location = 0) out vec4 outColor;
layout(set = 2, binding = 0) uniform sampler2D texSampler;
layout(set = 3, binding = 0) uniform UniformBufferObject {
bool flippedH;
bool flippedV;
vec2 uvOffset;
vec4 colorOverlay;
} ubo;
void main()
{
vec2 uv = inTexCoord;
uv += ubo.uvOffset;
if(ubo.flippedH) uv.x = 1.0 - uv.x;
if(ubo.flippedV) uv.y = 1.0 - uv.y;
outColor = texture(texSampler, uv) * inVertexColor * ubo.colorOverlay;
if(outColor.w < 0.1)
discard;
}

View File

@ -1,25 +0,0 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout (location = 0) in vec2 inPosition;
layout (location = 1) in vec2 inTexCoord;
layout(location = 0) out vec2 outTexCoord;
layout(location = 1) out vec4 outVertexColor;
layout(set = 1, binding = 0) uniform UBO_Vertex_PerScene {
mat4 projection;
} uboPerScene;
layout(set = 1, binding = 1) uniform UBO_Vertex_PerFrame {
mat4 view;
} uboPerFrame;
layout(set = 1, binding = 2) uniform UBO_Vertex_PerDraw {
mat4 model;
} uboPerDraw;
void main()
{
gl_Position = uboPerScene.projection * uboPerFrame.view * uboPerDraw.model * vec4(inPosition, 0.0f, 1.0f);
outTexCoord = inTexCoord;
//outVertexColor = inVertexColor;
}

View File

@ -29,36 +29,8 @@ namespace ia::iae::rpg
return &EngineConfig; return &EngineConfig;
} }
//Handle g_pathTile;
//Handle g_waterTile;
//Handle g_grassTile;
//Handle g_pathTileSheet;
//Handle g_waterTileSheet;
//Handle g_beachTileSheet;
//Handle g_cliffTileSheet;
//Handle g_farmLandTileSheet;
//Handle g_playerSpriteSheet;
VOID OnInitialize() VOID OnInitialize()
{ {
/*g_playerSpriteSheet = IAEngine::CreateSpriteSheet("Resources/Cute_Fantasy_Free/Player/Player.png", 32, 32,
{6, 6, 6, 6, 6, 6, 4, 4, 4, 4});
g_pathTile = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Path_Middle.png", 16, 16);
g_waterTile = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Water_Middle.png", 16, 16);
g_grassTile = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Grass_Middle.png", 16, 16);
g_pathTileSheet = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Path_Tile.png", 16, 16);
g_waterTileSheet = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Water_Tile.png", 16, 16);
g_beachTileSheet = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Beach_Tile.png", 16, 16);
g_cliffTileSheet = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/Cliff_Tile.png", 16, 16);
g_farmLandTileSheet = IAEngine::CreateTileSheet("Resources/Cute_Fantasy_Free/Tiles/FarmLand_Tile.png", 16, 16);
IAEngine::LoadResources({g_playerSpriteSheet, g_pathTile, g_waterTile, g_grassTile, g_pathTileSheet,
g_waterTileSheet, g_beachTileSheet, g_cliffTileSheet, g_farmLandTileSheet});
IAEngine::GetActiveScene()->SetupGrid({16, 16});
IAEngine::GetActiveScene()->GetGridCell(0, 0).TileSheetTexture = g_pathTile;*/
} }
VOID OnTerminate() VOID OnTerminate()
@ -75,7 +47,6 @@ namespace ia::iae::rpg
VOID OnUpdate(IN FLOAT32 deltaTime) VOID OnUpdate(IN FLOAT32 deltaTime)
{ {
//IAEngine::DrawSprite(0, 2, 0, {100.0f, 100.0f}, {1.0f, 1.0f}, 0.0f);
} }
VOID OnResize(IN INT32 newWidth, IN INT32 newHeight) VOID OnResize(IN INT32 newWidth, IN INT32 newHeight)

View File

@ -1,4 +1,5 @@
add_subdirectory(RenderCore/)
add_subdirectory(IAEngine/) add_subdirectory(IAEngine/)
add_subdirectory(CLI/) add_subdirectory(CLI/)
add_subdirectory(Editor/) add_subdirectory(Editor/)

View File

@ -0,0 +1,9 @@
set(SRC_FILES
"imp/cpp/Main.cpp"
)
add_executable(IAE_Editor ${SRC_FILES})
target_include_directories(IAE_Editor PRIVATE imp/hpp)
target_link_libraries(IAE_Editor PRIVATE RenderCore)

View File

@ -0,0 +1,61 @@
// 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 <https://www.gnu.org/licenses/>.
#include <SDL3/SDL.h>
#include <RenderCore/RenderCore.hpp>
namespace ia::iae
{
SDL_Window* g_windowHandle{};
INT32 Run(IN INT32 argc, IN PCCHAR argv[])
{
IVec2 windowExtent{800, 600};
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_GAMEPAD))
THROW_UNKNOWN("Failed to intialize SDL: ", SDL_GetError());
if (!(g_windowHandle = SDL_CreateWindow("IAEngine", windowExtent.x, windowExtent.y,
SDL_WINDOW_RESIZABLE)))
THROW_UNKNOWN("Failed to create the SDL window: ", SDL_GetError());
RDC::Initialize(windowExtent, g_windowHandle, true);
SDL_Event event{};
while (true)
{
SDL_PollEvent(&event);
if (event.type == SDL_EVENT_QUIT)
break;
//RDC::DrawSpriteTopLeft(0, 0, 0, {100.0f, 100.0f}, {1.0f, 1.0f}, 0.0f);
RDC::RenderToWindow();
}
RDC::Terminate();
SDL_DestroyWindow(g_windowHandle);
return 0;
}
}
int main(int argc, char* argv[])
{
return ia::iae::Run(argc, (const char**)argv);
}

View File

@ -2,9 +2,8 @@ set(SRC_FILES
"imp/cpp/IAEngine.cpp" "imp/cpp/IAEngine.cpp"
"imp/cpp/Scene.cpp" "imp/cpp/Scene.cpp"
"imp/cpp/Renderer.cpp"
"imp/cpp/GameData.cpp" "imp/cpp/GameData.cpp"
"imp/cpp/EmbeddedResources.cpp" #"imp/cpp/EmbeddedResources.cpp"
) )
add_library(IAEngine STATIC ${SRC_FILES}) add_library(IAEngine STATIC ${SRC_FILES})
@ -12,5 +11,5 @@ add_library(IAEngine STATIC ${SRC_FILES})
target_include_directories(IAEngine PUBLIC inc) target_include_directories(IAEngine PUBLIC inc)
target_include_directories(IAEngine PRIVATE imp/hpp) target_include_directories(IAEngine PRIVATE imp/hpp)
target_link_libraries(IAEngine PUBLIC IACore pugixml::pugixml glm::glm) target_link_libraries(IAEngine PUBLIC RenderCore pugixml::pugixml)
target_link_libraries(IAEngine PRIVATE ZLIB::ZLIBSTATIC SDL3::SDL3 SDL3_mixer::SDL3_mixer Freetype::Freetype) target_link_libraries(IAEngine PRIVATE ZLIB::ZLIBSTATIC SDL3_mixer::SDL3_mixer)

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

File diff suppressed because one or more lines are too long

View File

@ -14,14 +14,15 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <EmbeddedResources.hpp> //#include <EmbeddedResources.hpp>
#include <GameData.hpp> #include <GameData.hpp>
#include <IAEngine/LibInterface.hpp> #include <IAEngine/LibInterface.hpp>
#include <Renderer.hpp>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION
#include <Vendor/stb/stb_image.h> #include <Vendor/stb/stb_image.h>
#include <RenderCore/RenderCore.hpp>
namespace ia::iae namespace ia::iae
{ {
struct Resource struct Resource
@ -110,7 +111,7 @@ namespace ia::iae
const auto gameVersion = g_gameVersion; const auto gameVersion = g_gameVersion;
SDL_SetAppMetadata(g_gameName.c_str(), IA_STRINGIFY_VERSION(gameVersion).c_str(), g_gamePackageName.c_str()); SDL_SetAppMetadata(g_gameName.c_str(), IA_STRINGIFY_VERSION(gameVersion).c_str(), g_gamePackageName.c_str());
EmbeddedResources::Initialize(); //EmbeddedResources::Initialize();
IAEngine::Initialize(); IAEngine::Initialize();
@ -136,7 +137,7 @@ namespace ia::iae
IAEngine::Terminate(); IAEngine::Terminate();
EmbeddedResources::Terminate(); //EmbeddedResources::Terminate();
SDL_DestroyWindow(g_windowHandle); SDL_DestroyWindow(g_windowHandle);
@ -150,14 +151,7 @@ namespace ia::iae
{ {
VOID IAEngine::Initialize() VOID IAEngine::Initialize()
{ {
Renderer::Initialize(IVec2{g_designViewport.x, g_designViewport.y}); RDC::Initialize(IVec2{g_designViewport.x, g_designViewport.y}, g_windowHandle, g_isDebugMode);
{ // Add default texture to resources
const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE;
t->Texture = 0;
g_resources.pushBack(t);
}
GameData::Initialize(); GameData::Initialize();
@ -173,7 +167,7 @@ namespace ia::iae
GameData::Terminate(); GameData::Terminate();
Renderer::Terminate(); RDC::Terminate();
for (SIZE_T i = 0; i < g_resources.size(); i++) for (SIZE_T i = 0; i < g_resources.size(); i++)
DestroyResource(i); DestroyResource(i);
@ -182,7 +176,7 @@ namespace ia::iae
VOID IAEngine::Draw() VOID IAEngine::Draw()
{ {
g_activeScene->OnDraw(); g_activeScene->OnDraw();
Renderer::Draw(); RDC::RenderToWindow();
} }
VOID IAEngine::Update() VOID IAEngine::Update()
@ -219,7 +213,7 @@ namespace ia::iae
auto pixels = stbi_load(path, &w, &h, &n, STBI_rgb_alpha); auto pixels = stbi_load(path, &w, &h, &n, STBI_rgb_alpha);
if (!pixels) if (!pixels)
THROW_INVALID_DATA(path); THROW_INVALID_DATA(path);
const auto t = Renderer::CreateTexture(pixels, w, h, tileWidth == -1 ? 1 : w / tileWidth, const auto t = RDC::CreateImage(pixels, w, h, tileWidth == -1 ? 1 : w / tileWidth,
tileHeight == -1 ? 1 : h / tileHeight); tileHeight == -1 ? 1 : h / tileHeight);
stbi_image_free(pixels); stbi_image_free(pixels);
return t; return t;
@ -238,7 +232,7 @@ namespace ia::iae
{ {
const auto t = new Resource_Texture(); const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE; t->Type = Resource::EType::TEXTURE;
t->Texture = Renderer::CreateTexture(rgbaData, width, height); t->Texture = RDC::CreateImage(rgbaData, width, height);
g_resources.pushBack(t); g_resources.pushBack(t);
return (Handle) g_resources.size() - 1; return (Handle) g_resources.size() - 1;
} }
@ -274,7 +268,7 @@ namespace ia::iae
{ {
const auto t = new Resource_Texture(); const auto t = new Resource_Texture();
t->Type = Resource::EType::TEXTURE; t->Type = Resource::EType::TEXTURE;
t->Texture = Renderer::CreateTexture(rgbaData, width, height, width / tileWidth, height / tileHeight); t->Texture = RDC::CreateImage(rgbaData, width, height, width / tileWidth, height / tileHeight);
g_resources.pushBack(t); g_resources.pushBack(t);
return (Handle) g_resources.size() - 1; return (Handle) g_resources.size() - 1;
} }
@ -291,7 +285,7 @@ namespace ia::iae
case Resource::EType::TEXTURE: case Resource::EType::TEXTURE:
case Resource::EType::SPRITE_SHEET: case Resource::EType::SPRITE_SHEET:
Renderer::DestroyTexture(static_cast<Resource_Texture *>(g_resources[resource])->Texture); RDC::DestroyImage(static_cast<Resource_Texture *>(g_resources[resource])->Texture);
break; break;
case Resource::EType::SOUND: case Resource::EType::SOUND:
@ -341,7 +335,7 @@ namespace ia::iae
} }
} }
Renderer::BakeTextureAtlas(atlasTextures); RDC::CompileTextures(atlasTextures);
} }
} // namespace ia::iae } // namespace ia::iae
@ -351,7 +345,7 @@ namespace ia::iae
IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset) IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{ {
const auto t = static_cast<Resource_Texture *>(g_resources[tileSheet]); const auto t = static_cast<Resource_Texture *>(g_resources[tileSheet]);
Renderer::DrawStaticSpriteTopLeft(t->Texture, tileIndexX, tileIndexY, position, scale, rotation, flipH, flipV, RDC::DrawSpriteTopLeft(t->Texture, tileIndexX, tileIndexY, position, scale, rotation, flipH, flipV,
uvOffset); uvOffset);
} }
@ -359,14 +353,14 @@ namespace ia::iae
IN BOOL flipV, IN Vec2 uvOffset) IN BOOL flipV, IN Vec2 uvOffset)
{ {
const auto t = static_cast<Resource_Texture *>(g_resources[sprite]); const auto t = static_cast<Resource_Texture *>(g_resources[sprite]);
Renderer::DrawDynamicSpriteTopLeft(t->Texture, 0, 0, position, scale, rotation, flipH, flipV, uvOffset); RDC::DrawSpriteTopLeft(t->Texture, 0, 0, position, scale, rotation, flipH, flipV, uvOffset);
} }
VOID IAEngine::DrawSprite(IN Handle spriteSheet, IN INT32 animationIndex, IN INT32 frameIndex, IN Vec2 position, VOID IAEngine::DrawSprite(IN Handle spriteSheet, IN INT32 animationIndex, IN INT32 frameIndex, IN Vec2 position,
IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset) IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{ {
const auto t = static_cast<Resource_SpriteSheet *>(g_resources[spriteSheet]); const auto t = static_cast<Resource_SpriteSheet *>(g_resources[spriteSheet]);
Renderer::DrawDynamicSpriteTopLeft(t->Texture, frameIndex, animationIndex, position, scale, rotation, flipH, RDC::DrawSpriteTopLeft(t->Texture, frameIndex, animationIndex, position, scale, rotation, flipH,
flipV, uvOffset); flipV, uvOffset);
} }
} // namespace ia::iae } // namespace ia::iae

View File

@ -1,746 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
#include <Renderer.hpp>
#include <EmbeddedResources.hpp>
namespace ia::iae
{
STATIC CONSTEXPR INT32 MAX_SPRITE_COUNT = 100000;
#pragma pack(push, 1)
struct SpriteInstanceData
{
Mat4 Transform{1.0f};
Vec4 TexCoords{};
Vec4 Color{1.0f, 1.0f, 1.0f, 1.0f};
};
#pragma pack(pop)
struct TextureData
{
PUINT8 Pixels{};
INT32 Width{};
INT32 Height{};
INT32 TileWidth{};
INT32 TileHeight{};
INT32 TileCountX{};
INT32 TileCountY{};
SDL_GPUTexture *BakedHandle{};
};
} // namespace ia::iae
namespace ia::iae
{
IVec2 g_screenExtent{};
SDL_GPUDevice *g_gpuDevice{};
RenderPipeline *g_debugPipeline{};
RenderPipeline *g_geometryPipeline{};
SDL_GPUSampler *g_linearClampSampler{};
SDL_GPUSampler *g_linearRepeatSampler{};
Mat4 g_viewMatrix{1.0f};
Vec2 g_cameraPosition{};
Mat4 g_projectionMatrix{1.0f};
Geometry *g_quadGeometry;
Vector<TextureData> g_texureData;
Vector<SpriteInstanceData> g_staticSprites;
Vector<SpriteInstanceData> g_dynamicSprites;
SDL_GPUBuffer *g_staticSpriteDataBuffer{};
SDL_GPUBuffer *g_dynamicSpriteDataBuffer{};
SDL_GPUTransferBuffer *g_spriteDataStagingBuffer{};
SDL_GPUTexture *g_defaultTexture{};
Vec2 g_activeTextureAtlasInverseSize{};
SDL_GPUTexture *g_activeTextureAtlas{};
Map<Handle, Vec2> g_activeTextureAtlasUVMap;
EXTERN BOOL g_isDebugMode;
EXTERN Vec2 g_designViewport;
EXTERN SDL_Window *g_windowHandle;
} // namespace ia::iae
namespace ia::iae
{
VOID Renderer::Initialize(IN IVec2 screenExtent)
{
g_screenExtent = screenExtent;
InitializeGPU();
InitializeSampler();
InitializePipelines();
InitializeGeometries();
InitializeDrawData();
InitializeTextures();
}
VOID Renderer::Terminate()
{
SDL_WaitForGPUIdle(g_gpuDevice);
delete g_debugPipeline;
delete g_geometryPipeline;
for (SIZE_T i = 0; i < g_texureData.size(); i++)
DestroyTexture(i);
if (g_activeTextureAtlas && (g_activeTextureAtlas != g_defaultTexture))
DestroyTexture(g_activeTextureAtlas);
DestroyBuffer(g_staticSpriteDataBuffer);
DestroyBuffer(g_dynamicSpriteDataBuffer);
SDL_ReleaseGPUTransferBuffer(g_gpuDevice, g_spriteDataStagingBuffer);
SDL_ReleaseGPUSampler(g_gpuDevice, g_linearClampSampler);
SDL_ReleaseGPUSampler(g_gpuDevice, g_linearRepeatSampler);
DestroyGeometry(g_quadGeometry);
SDL_ReleaseWindowFromGPUDevice(g_gpuDevice, g_windowHandle);
SDL_DestroyGPUDevice(g_gpuDevice);
}
VOID Renderer::Draw()
{
Render();
}
VOID Renderer::Render()
{
STATIC SDL_GPURenderPass *ActiveRenderPass{};
STATIC SDL_GPUCommandBuffer *ActiveCommandBuffer{};
STATIC SDL_GPUColorTargetInfo ActiveColorTargetInfo{.clear_color = SDL_FColor{0.0f, 0.0f, 0.0f, 1.0f},
.load_op = SDL_GPU_LOADOP_CLEAR,
.store_op = SDL_GPU_STOREOP_STORE};
if (!(ActiveCommandBuffer = SDL_AcquireGPUCommandBuffer(g_gpuDevice)))
THROW_UNKNOWN("Failed to acquire SDL GPU command buffer: ", SDL_GetError());
SDL_GPUTexture *swapChainTexture{};
if (!SDL_WaitAndAcquireGPUSwapchainTexture(ActiveCommandBuffer, g_windowHandle, &swapChainTexture, nullptr,
nullptr))
THROW_UNKNOWN("Failed to acquire SDL GPU Swapchain texture: ", SDL_GetError());
if (!swapChainTexture)
return;
ActiveColorTargetInfo.texture = swapChainTexture;
ActiveColorTargetInfo.clear_color = SDL_FColor{0.3f, 0.3f, 0.3f, 1.0f};
ActiveRenderPass = SDL_BeginGPURenderPass(ActiveCommandBuffer, &ActiveColorTargetInfo, 1, nullptr);
SDL_BindGPUGraphicsPipeline(ActiveRenderPass, g_debugPipeline->GetHandle());
SDL_PushGPUVertexUniformData(ActiveCommandBuffer, 0, &g_projectionMatrix, sizeof(Mat4));
SDL_PushGPUVertexUniformData(ActiveCommandBuffer, 1, &g_viewMatrix, sizeof(Mat4));
SDL_GPUBufferBinding bufferBindings[] = {{.buffer = ((Geometry *) g_quadGeometry)->VertexBuffer, .offset = 0},
{.buffer = ((Geometry *) g_quadGeometry)->IndexBuffer, .offset = 0}};
SDL_BindGPUVertexBuffers(ActiveRenderPass, 0, &bufferBindings[0], 1);
SDL_BindGPUIndexBuffer(ActiveRenderPass, &bufferBindings[1], SDL_GPU_INDEXELEMENTSIZE_32BIT);
SDL_GPUTextureSamplerBinding textureBinding{.texture = g_activeTextureAtlas,
.sampler = GetSampler_LinearRepeat()};
SDL_BindGPUFragmentSamplers(ActiveRenderPass, 0, &textureBinding, 1);
if (g_staticSprites.size())
{
CopyToDeviceLocalBuffer(g_spriteDataStagingBuffer, g_staticSpriteDataBuffer, g_staticSprites.data(),
g_staticSprites.size() * sizeof(SpriteInstanceData));
SDL_BindGPUVertexStorageBuffers(ActiveRenderPass, 0, &g_staticSpriteDataBuffer, 1);
SDL_DrawGPUIndexedPrimitives(ActiveRenderPass, g_quadGeometry->IndexCount, g_staticSprites.size(), 0, 0, 0);
}
if (g_dynamicSprites.size())
{
CopyToDeviceLocalBuffer(g_spriteDataStagingBuffer, g_dynamicSpriteDataBuffer, g_dynamicSprites.data(),
g_dynamicSprites.size() * sizeof(SpriteInstanceData));
SDL_BindGPUVertexStorageBuffers(ActiveRenderPass, 0, &g_dynamicSpriteDataBuffer, 1);
SDL_DrawGPUIndexedPrimitives(ActiveRenderPass, g_quadGeometry->IndexCount, g_dynamicSprites.size(), 0, 0,
0);
}
g_staticSprites.resize(0);
g_dynamicSprites.resize(0);
SDL_EndGPURenderPass(ActiveRenderPass);
SDL_SubmitGPUCommandBuffer(ActiveCommandBuffer);
}
Vec2 Renderer::GetCameraPosition()
{
return g_cameraPosition;
}
VOID Renderer::SetCameraPosition(IN Vec2 position)
{
if B_LIKELY (g_cameraPosition == position)
return;
g_cameraPosition = position;
g_viewMatrix = glm::lookAtLH(glm::vec3{g_cameraPosition, -1.0f}, {g_cameraPosition, 0.0f}, {0.0f, 1.0f, 0.0f});
}
} // namespace ia::iae
namespace ia::iae
{
Vec2 Renderer::DrawStaticSpriteTopLeft(IN Handle texture, 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 _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].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));
g_staticSprites.pushBack(
{.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Vec2 Renderer::DrawStaticSpriteCentered(IN Handle texture, 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 _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].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));
g_staticSprites.pushBack(
{.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Vec2 Renderer::DrawDynamicSpriteTopLeft(IN Handle texture, 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 _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].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});
g_dynamicSprites.pushBack(
{.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Vec2 Renderer::DrawDynamicSpriteCentered(IN Handle texture, 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 _s = Vec2{scale.x * g_texureData[texture].TileWidth, scale.y * g_texureData[texture].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});
g_dynamicSprites.pushBack(
{.Transform = transform,
.TexCoords = GetTextureAtlasCoordinates(texture, tileIndexX, tileIndexY, flipH, flipV, uvOffset),
.Color = {1.0f, 1.0f, 1.0f, 1.0f}});
return _s;
}
Geometry *Renderer::CreateGeometry(IN CONST Vector<GeometryVertex> &vertices, IN CONST Vector<INT32> &indices)
{
const auto mesh = new Geometry();
mesh->VertexBuffer = CreateDeviceLocalBuffer(SDL_GPU_BUFFERUSAGE_VERTEX, vertices.data(),
static_cast<UINT32>(vertices.size() * sizeof(vertices[0])));
mesh->IndexBuffer = CreateDeviceLocalBuffer(SDL_GPU_BUFFERUSAGE_INDEX, indices.data(),
static_cast<UINT32>(indices.size() * sizeof(indices[0])));
mesh->IndexCount = static_cast<UINT32>(indices.size());
return mesh;
}
VOID Renderer::DestroyGeometry(IN Geometry *geometry)
{
DestroyBuffer(geometry->VertexBuffer);
DestroyBuffer(geometry->IndexBuffer);
delete geometry;
}
} // namespace ia::iae
namespace ia::iae
{
Handle Renderer::CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileCountX,
IN INT32 tileCountY)
{
const auto pixelDataSize = width * height * 4;
g_texureData.pushBack({.Pixels = new UINT8[pixelDataSize],
.Width = width,
.Height = height,
.TileWidth = width / tileCountX,
.TileHeight = height / tileCountY,
.TileCountX = tileCountX,
.TileCountY = tileCountY,
.BakedHandle = nullptr});
ia_memcpy(g_texureData.back().Pixels, rgbaData, pixelDataSize);
return g_texureData.size() - 1;
}
VOID Renderer::DestroyTexture(IN Handle texture)
{
auto &t = g_texureData[texture];
if (t.Pixels)
delete[] t.Pixels;
if (t.BakedHandle)
DestroyTexture(t.BakedHandle);
t.Pixels = nullptr;
t.BakedHandle = nullptr;
}
VOID Renderer::BakeTexture(IN Handle texture)
{
auto &t = g_texureData[texture];
t.BakedHandle = CreateTexture(SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET, t.Width,
t.Height, t.Width, t.Pixels);
}
VOID Renderer::BakeTextureAtlas(IN CONST Vector<Handle> textures)
{
if (textures.empty())
return;
if (g_activeTextureAtlas && (g_activeTextureAtlas != g_defaultTexture))
DestroyTexture(g_activeTextureAtlas);
g_activeTextureAtlasUVMap = Map<Handle, Vec2>();
INT32 atlasWidth{0}, atlasHeight{0};
for (const auto &t : textures)
{
const auto &d = g_texureData[t];
atlasWidth += d.Width;
if (d.Height > atlasHeight)
atlasHeight = d.Height;
}
g_activeTextureAtlasInverseSize = {1.0f / ((FLOAT32) atlasWidth), 1.0f / ((FLOAT32) atlasHeight)};
const auto pixels = new UINT8[atlasWidth * atlasHeight * 4];
INT32 atlasCursor{0};
for (const auto &t : textures)
{
const auto &d = g_texureData[t];
for (INT32 y = 0; y < d.Height; y++)
ia_memcpy(&pixels[(atlasCursor + (y * atlasWidth)) * 4], &d.Pixels[y * d.Width * 4], d.Width * 4);
g_activeTextureAtlasUVMap[t] = Vec2(((FLOAT32) atlasCursor) / ((FLOAT32) atlasWidth), 0.0f);
atlasCursor += d.Width;
}
g_activeTextureAtlas = CreateTexture(SDL_GPU_TEXTUREUSAGE_SAMPLER | SDL_GPU_TEXTUREUSAGE_COLOR_TARGET,
atlasWidth, atlasHeight, atlasWidth, pixels);
delete[] pixels;
}
Vec4 Renderer::GetTextureAtlasCoordinates(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset)
{
const auto &d = g_texureData[texture];
const auto &t = g_activeTextureAtlasUVMap[texture];
const auto pX = ((tileIndexX + uvOffset.x) * ((FLOAT32) d.TileWidth)) * g_activeTextureAtlasInverseSize.x;
const auto pY = ((tileIndexY + uvOffset.y) * ((FLOAT32) d.TileHeight)) * g_activeTextureAtlasInverseSize.y;
auto texCoords = Vec4(t.x + pX, t.y + pY, d.TileWidth * g_activeTextureAtlasInverseSize.x,
d.TileHeight * g_activeTextureAtlasInverseSize.y);
if (flipH)
{
texCoords.x += texCoords.z;
texCoords.z *= -1;
}
if (flipV)
{
texCoords.y += texCoords.w;
texCoords.w *= -1;
}
return texCoords;
}
} // namespace ia::iae
namespace ia::iae
{
SDL_GPUTexture *Renderer::CreateTexture(IN SDL_GPUTextureUsageFlags usage, IN INT32 width, IN INT32 height,
IN INT32 stride, IN PCUINT8 rgbaData, IN SDL_GPUTextureFormat format,
IN BOOL generateMipmaps)
{
const auto mipLevels =
generateMipmaps ? ia_max((UINT32) (floor(log2(ia_max(width, height))) + 1), (UINT32) 1) : (UINT32) 1;
STATIC Vector<UINT8> TMP_COLOR_BUFFER;
SDL_GPUTextureCreateInfo createInfo{.type = SDL_GPU_TEXTURETYPE_2D,
.format = format,
.usage = usage,
.width = (UINT32) width,
.height = (UINT32) height,
.layer_count_or_depth = 1,
.num_levels = (UINT32) mipLevels,
.sample_count = SDL_GPU_SAMPLECOUNT_1};
const auto result = SDL_CreateGPUTexture(g_gpuDevice, &createInfo);
if (!result)
{
THROW_UNKNOWN("Failed to create a SDL GPU Texture: ", SDL_GetError());
return nullptr;
}
if (rgbaData)
{
TMP_COLOR_BUFFER.resize(width * height * 4);
if (stride == width)
{
for (SIZE_T i = 0; i < TMP_COLOR_BUFFER.size() >> 2; i++)
{
const auto a = static_cast<FLOAT32>(rgbaData[i * 4 + 3]) / 255.0f;
TMP_COLOR_BUFFER[i * 4 + 0] = static_cast<UINT8>(rgbaData[i * 4 + 0] * a);
TMP_COLOR_BUFFER[i * 4 + 1] = static_cast<UINT8>(rgbaData[i * 4 + 1] * a);
TMP_COLOR_BUFFER[i * 4 + 2] = static_cast<UINT8>(rgbaData[i * 4 + 2] * a);
TMP_COLOR_BUFFER[i * 4 + 3] = rgbaData[i * 4 + 3];
}
}
else
{
for (INT32 y = 0; y < height; y++)
{
for (INT32 x = 0; x < width; x++)
{
const auto p = &rgbaData[(x + y * stride) * 4];
const auto a = static_cast<FLOAT32>(p[3]) / 255.0f;
TMP_COLOR_BUFFER[(x + y * width) * 4 + 0] = static_cast<UINT8>(p[0] * a);
TMP_COLOR_BUFFER[(x + y * width) * 4 + 1] = static_cast<UINT8>(p[1] * a);
TMP_COLOR_BUFFER[(x + y * width) * 4 + 2] = static_cast<UINT8>(p[2] * a);
TMP_COLOR_BUFFER[(x + y * width) * 4 + 3] = p[3];
}
}
}
SDL_GPUTransferBufferCreateInfo stagingBufferCreateInfo{.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = (UINT32) width * (UINT32) height * 4};
const auto stagingBuffer = SDL_CreateGPUTransferBuffer(g_gpuDevice, &stagingBufferCreateInfo);
const auto mappedPtr = SDL_MapGPUTransferBuffer(g_gpuDevice, stagingBuffer, false);
SDL_memcpy(mappedPtr, TMP_COLOR_BUFFER.data(), width * height * 4);
SDL_UnmapGPUTransferBuffer(g_gpuDevice, stagingBuffer);
auto cmdBuffer = SDL_AcquireGPUCommandBuffer(g_gpuDevice);
const auto copyPass = SDL_BeginGPUCopyPass(cmdBuffer);
SDL_GPUTextureTransferInfo transferInfo{.transfer_buffer = stagingBuffer, .offset = 0};
SDL_GPUTextureRegion region{.texture = result, .w = (UINT32) width, .h = (UINT32) height, .d = 1};
SDL_UploadToGPUTexture(copyPass, &transferInfo, &region, false);
SDL_EndGPUCopyPass(copyPass);
SDL_SubmitGPUCommandBuffer(cmdBuffer);
SDL_WaitForGPUIdle(g_gpuDevice);
SDL_ReleaseGPUTransferBuffer(g_gpuDevice, stagingBuffer);
if (mipLevels > 1)
{
cmdBuffer = SDL_AcquireGPUCommandBuffer(g_gpuDevice);
SDL_GenerateMipmapsForGPUTexture(cmdBuffer, result);
SDL_SubmitGPUCommandBuffer(cmdBuffer);
SDL_WaitForGPUIdle(g_gpuDevice);
}
}
return result;
}
SDL_GPUBuffer *Renderer::CreateDeviceLocalBuffer(IN SDL_GPUBufferUsageFlags usage, IN PCVOID data,
IN UINT32 dataSize)
{
SDL_GPUBufferCreateInfo createInfo{.usage = usage, .size = dataSize};
const auto result = SDL_CreateGPUBuffer(g_gpuDevice, &createInfo);
if (!result)
{
THROW_UNKNOWN("Failed to create a SDL GPU Buffer: ", SDL_GetError());
return nullptr;
}
if (data && dataSize)
CopyToDeviceLocalBuffer(result, data, dataSize);
return result;
}
VOID Renderer::CopyToDeviceLocalBuffer(IN SDL_GPUBuffer *buffer, IN PCVOID data, IN UINT32 dataSize)
{
SDL_GPUTransferBufferCreateInfo stagingBufferCreateInfo{.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = dataSize};
const auto stagingBuffer = SDL_CreateGPUTransferBuffer(g_gpuDevice, &stagingBufferCreateInfo);
CopyToDeviceLocalBuffer(stagingBuffer, buffer, data, dataSize);
SDL_ReleaseGPUTransferBuffer(g_gpuDevice, stagingBuffer);
}
VOID Renderer::CopyToDeviceLocalBuffer(IN SDL_GPUTransferBuffer *stagingBuffer, IN SDL_GPUBuffer *buffer,
IN PCVOID data, IN UINT32 dataSize)
{
const auto mappedPtr = SDL_MapGPUTransferBuffer(g_gpuDevice, stagingBuffer, false);
SDL_memcpy(mappedPtr, data, dataSize);
SDL_UnmapGPUTransferBuffer(g_gpuDevice, stagingBuffer);
const auto cmdBuffer = SDL_AcquireGPUCommandBuffer(g_gpuDevice);
const auto copyPass = SDL_BeginGPUCopyPass(cmdBuffer);
SDL_GPUTransferBufferLocation src{.transfer_buffer = stagingBuffer, .offset = 0};
SDL_GPUBufferRegion dst{.buffer = buffer, .offset = 0, .size = dataSize};
SDL_UploadToGPUBuffer(copyPass, &src, &dst, false);
SDL_EndGPUCopyPass(copyPass);
SDL_SubmitGPUCommandBuffer(cmdBuffer);
SDL_WaitForGPUIdle(g_gpuDevice);
}
VOID Renderer::DestroyTexture(IN SDL_GPUTexture *handle)
{
if (!handle)
return;
SDL_ReleaseGPUTexture(g_gpuDevice, handle);
}
VOID Renderer::DestroyBuffer(IN SDL_GPUBuffer *handle)
{
if (!handle)
return;
SDL_ReleaseGPUBuffer(g_gpuDevice, handle);
}
SDL_GPUSampler *Renderer::GetSampler_LinearClamp()
{
return g_linearClampSampler;
}
SDL_GPUSampler *Renderer::GetSampler_LinearRepeat()
{
return g_linearRepeatSampler;
}
} // namespace ia::iae
namespace ia::iae
{
VOID Renderer::InitializePipelines()
{
g_debugPipeline = new RenderPipeline(
RenderPipeline::StageDesc{
.SourceData = EmbeddedResources::GetResource("Shaders/Debug.vert"),
.SamplerCount = 0,
.UniformBufferCount = 2,
.StorageBufferCount = 1,
},
RenderPipeline::StageDesc{
.SourceData = EmbeddedResources::GetResource("Shaders/Debug.frag"),
.SamplerCount = 1,
.UniformBufferCount = 0,
.StorageBufferCount = 0,
},
true);
g_geometryPipeline = new RenderPipeline(
RenderPipeline::StageDesc{
.SourceData = EmbeddedResources::GetResource("Shaders/Geometry.vert"),
.SamplerCount = 0,
.UniformBufferCount = 3,
.StorageBufferCount = 0,
},
RenderPipeline::StageDesc{
.SourceData = EmbeddedResources::GetResource("Shaders/Geometry.frag"),
.SamplerCount = 1,
.UniformBufferCount = 1,
.StorageBufferCount = 0,
},
true);
}
RenderPipeline::RenderPipeline(IN CONST StageDesc &vertexStageDesc, IN CONST StageDesc &pixelStageDesc,
IN BOOL enableVertexBuffer)
{
SDL_GPUShader *vertexShader{};
SDL_GPUShader *pixelShader{};
SDL_GPUShaderCreateInfo shaderCreateInfo = {
.entrypoint = "main",
.format = SDL_GPU_SHADERFORMAT_SPIRV,
.num_storage_textures = 0,
.num_storage_buffers = 0,
};
shaderCreateInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX;
shaderCreateInfo.code = vertexStageDesc.SourceData.data();
shaderCreateInfo.code_size = vertexStageDesc.SourceData.size();
shaderCreateInfo.num_samplers = vertexStageDesc.SamplerCount;
shaderCreateInfo.num_uniform_buffers = vertexStageDesc.UniformBufferCount;
shaderCreateInfo.num_storage_buffers = vertexStageDesc.StorageBufferCount;
if (!(vertexShader = SDL_CreateGPUShader(g_gpuDevice, &shaderCreateInfo)))
THROW_UNKNOWN("Failed to create a SDL shader: ", SDL_GetError());
shaderCreateInfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
shaderCreateInfo.code = pixelStageDesc.SourceData.data();
shaderCreateInfo.code_size = pixelStageDesc.SourceData.size();
shaderCreateInfo.num_samplers = pixelStageDesc.SamplerCount;
shaderCreateInfo.num_uniform_buffers = pixelStageDesc.UniformBufferCount;
shaderCreateInfo.num_storage_buffers = pixelStageDesc.StorageBufferCount;
if (!(pixelShader = SDL_CreateGPUShader(g_gpuDevice, &shaderCreateInfo)))
THROW_UNKNOWN("Failed to create a SDL shader: ", SDL_GetError());
SDL_GPUColorTargetDescription colorTargetDesc = {
.format = SDL_GetGPUSwapchainTextureFormat(g_gpuDevice, g_windowHandle),
.blend_state = {.src_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.color_blend_op = SDL_GPU_BLENDOP_ADD,
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
.enable_blend = true,
.enable_color_write_mask = false}};
SDL_GPUVertexBufferDescription vertexBufferDesc = {
.slot = 0,
.pitch = sizeof(GeometryVertex),
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
.instance_step_rate = 0,
};
SDL_GPUVertexAttribute vertexAttributes[] = {
{.location = 0, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = 0},
{.location = 1, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = sizeof(Vec2)}};
SDL_GPUGraphicsPipelineCreateInfo createInfo = {
.vertex_shader = vertexShader,
.fragment_shader = pixelShader,
.vertex_input_state = SDL_GPUVertexInputState{.vertex_buffer_descriptions = &vertexBufferDesc,
.num_vertex_buffers = enableVertexBuffer ? (UINT32) 1 : 0,
.vertex_attributes = vertexAttributes,
.num_vertex_attributes = enableVertexBuffer ? (UINT32) 2 : 0},
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.rasterizer_state = SDL_GPURasterizerState{.fill_mode = SDL_GPU_FILLMODE_FILL,
.cull_mode = SDL_GPU_CULLMODE_NONE,
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
.enable_depth_clip = true},
.target_info = {.color_target_descriptions = &colorTargetDesc,
.num_color_targets = 1,
.has_depth_stencil_target = false},
};
if (!(m_handle = SDL_CreateGPUGraphicsPipeline(g_gpuDevice, &createInfo)))
THROW_UNKNOWN("Failed to create a SDL graphics pipeline: ", SDL_GetError());
SDL_ReleaseGPUShader(g_gpuDevice, pixelShader);
SDL_ReleaseGPUShader(g_gpuDevice, vertexShader);
}
RenderPipeline::~RenderPipeline()
{
SDL_ReleaseGPUGraphicsPipeline(g_gpuDevice, m_handle);
}
} // namespace ia::iae
namespace ia::iae
{
VOID Renderer::InitializeGPU()
{
SDL_PropertiesID deviceCreateProps = SDL_CreateProperties();
SDL_SetStringProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, nullptr);
SDL_SetBooleanProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true);
SDL_SetBooleanProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, g_isDebugMode);
SDL_SetBooleanProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, false);
if (!(g_gpuDevice = SDL_CreateGPUDeviceWithProperties(deviceCreateProps)))
THROW_UNKNOWN("Failed to create the SDL GPU Device: ", SDL_GetError());
SDL_DestroyProperties(deviceCreateProps);
if (!SDL_ClaimWindowForGPUDevice(g_gpuDevice, g_windowHandle))
THROW_UNKNOWN("Failed to initialize SDL GPU for the window: ", SDL_GetError());
SDL_SetGPUSwapchainParameters(g_gpuDevice, g_windowHandle, SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
SDL_GPU_PRESENTMODE_VSYNC);
}
VOID Renderer::InitializeSampler()
{
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};
g_linearClampSampler = SDL_CreateGPUSampler(g_gpuDevice, &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,
g_linearRepeatSampler = SDL_CreateGPUSampler(g_gpuDevice, &createInfo);
}
VOID Renderer::InitializeGeometries()
{
g_quadGeometry = Renderer::CreateGeometry(
{
{glm::vec2{0, 1}, glm::vec2{0, 1}},
{glm::vec2{1, 1}, glm::vec2{1, 1}},
{glm::vec2{1, 0}, glm::vec2{1, 0}},
{glm::vec2{0, 0}, glm::vec2{0, 0}},
},
{0, 1, 2, 2, 3, 0});
}
VOID Renderer::InitializeDrawData()
{
g_staticSpriteDataBuffer = CreateDeviceLocalBuffer(SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ, nullptr,
sizeof(SpriteInstanceData) * MAX_SPRITE_COUNT);
g_dynamicSpriteDataBuffer = CreateDeviceLocalBuffer(SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ, nullptr,
sizeof(SpriteInstanceData) * MAX_SPRITE_COUNT);
SDL_GPUTransferBufferCreateInfo stagingBufferCreateInfo{.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = sizeof(SpriteInstanceData) * MAX_SPRITE_COUNT};
g_spriteDataStagingBuffer = SDL_CreateGPUTransferBuffer(g_gpuDevice, &stagingBufferCreateInfo);
g_projectionMatrix =
glm::orthoLH(0.0f, (FLOAT32) g_screenExtent.x, (FLOAT32) g_screenExtent.y, 0.0f, -1.0f, 1.0f);
SetCameraPosition({});
}
VOID Renderer::InitializeTextures()
{
{ // Create Default Texture
const auto pixels = new UINT8[100 * 100 * 4];
ia_memset(pixels, 0xFF, 100 * 100 * 4);
const auto t = CreateTexture(pixels, 100, 100);
BakeTexture(t);
g_defaultTexture = g_texureData[t].BakedHandle;
delete[] pixels;
}
g_activeTextureAtlas = g_defaultTexture;
}
} // namespace ia::iae

View File

@ -15,7 +15,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <IAEngine/IAEngine.hpp> #include <IAEngine/IAEngine.hpp>
#include <Renderer.hpp>
namespace ia::iae namespace ia::iae
{ {
@ -65,6 +64,6 @@ namespace ia::iae
VOID Scene::OnUpdate(IN FLOAT32 deltaTime) VOID Scene::OnUpdate(IN FLOAT32 deltaTime)
{ {
Renderer::SetCameraPosition(m_cameraPosition); //Renderer::SetCameraPosition(m_cameraPosition);
} }
} // namespace ia::iae } // namespace ia::iae

View File

@ -1,130 +0,0 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <IAEngine/Base.hpp>
#include <SDL3/SDL.h>
namespace ia::iae
{
struct GeometryVertex
{
Vec2 Position;
Vec2 TexCoords;
};
struct Geometry
{
INT32 IndexCount{};
SDL_GPUBuffer *IndexBuffer;
SDL_GPUBuffer *VertexBuffer;
};
class RenderPipeline
{
public:
struct StageDesc
{
Vector<UINT8> SourceData{};
UINT32 SamplerCount{};
UINT32 UniformBufferCount{};
UINT32 StorageBufferCount{};
};
public:
RenderPipeline(IN CONST StageDesc &vertexStageDesc, IN CONST StageDesc &pixelStageDesc,
IN BOOL enableVertexBuffer);
~RenderPipeline();
SDL_GPUGraphicsPipeline *GetHandle() CONST
{
return m_handle;
}
private:
SDL_GPUGraphicsPipeline *m_handle{};
};
class Renderer
{
public:
STATIC Vec2 GetCameraPosition();
STATIC VOID SetCameraPosition(IN Vec2 position);
public:
STATIC Vec2 DrawStaticSpriteTopLeft(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Vec2 DrawStaticSpriteCentered(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Vec2 DrawDynamicSpriteTopLeft(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Vec2 DrawDynamicSpriteCentered(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC VOID ResizeScreen(IN IVec2 newSize);
public:
STATIC Handle CreateTexture(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileCountX = 1,
IN INT32 tileCountY = 1);
STATIC VOID DestroyTexture(IN Handle texture);
STATIC VOID BakeTexture(IN Handle texture);
STATIC VOID BakeTextureAtlas(IN CONST Vector<Handle> textures);
private:
STATIC SDL_GPUTexture *CreateTexture(IN SDL_GPUTextureUsageFlags usage, IN INT32 width, IN INT32 height,
IN INT32 stride, IN PCUINT8 rgbaData = nullptr,
IN SDL_GPUTextureFormat format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
IN BOOL generateMipmaps = false);
STATIC SDL_GPUBuffer *CreateDeviceLocalBuffer(IN SDL_GPUBufferUsageFlags usage, IN PCVOID data,
IN UINT32 dataSize);
STATIC VOID DestroyTexture(IN SDL_GPUTexture *handle);
STATIC VOID DestroyBuffer(IN SDL_GPUBuffer *handle);
STATIC VOID CopyToDeviceLocalBuffer(IN SDL_GPUBuffer *buffer, IN PCVOID data, IN UINT32 dataSize);
STATIC VOID CopyToDeviceLocalBuffer(IN SDL_GPUTransferBuffer *stagingBuffer, IN SDL_GPUBuffer *buffer,
IN PCVOID data, IN UINT32 dataSize);
STATIC SDL_GPUSampler *GetSampler_LinearClamp();
STATIC SDL_GPUSampler *GetSampler_LinearRepeat();
private:
STATIC VOID InitializeGPU();
STATIC VOID InitializeSampler();
STATIC VOID InitializePipelines();
STATIC VOID InitializeGeometries();
STATIC VOID InitializeDrawData();
STATIC VOID InitializeTextures();
STATIC Geometry *CreateGeometry(IN CONST Vector<GeometryVertex> &vertices, IN CONST Vector<INT32> &indices);
STATIC VOID DestroyGeometry(IN Geometry *geometry);
STATIC VOID Render();
STATIC Vec4 GetTextureAtlasCoordinates(IN Handle texture, IN INT32 tileIndexX, IN INT32 tileIndexY, IN BOOL flipH, IN BOOL flipV, IN Vec2 uvOffset);
private:
STATIC VOID Initialize(IN IVec2 screenExtent);
STATIC VOID Terminate();
STATIC VOID Draw();
friend class IAEngine;
};
} // namespace ia::iae

View File

@ -16,20 +16,7 @@
#pragma once #pragma once
#include <IACore/Exception.hpp> #include <RenderCore/Base.hpp>
#include <IACore/Logger.hpp>
#include <IACore/Map.hpp>
#include <IACore/Memory.hpp>
#include <IACore/String.hpp>
#include <IACore/Vector.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/scalar_constants.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#define IAE_LOG_TAG "IAE" #define IAE_LOG_TAG "IAE"
@ -40,14 +27,4 @@
namespace ia::iae namespace ia::iae
{ {
using Handle = INT64;
STATIC CONSTEXPR Handle INVALID_HANDLE = -1;
using Vec2 = glm::vec2;
using Vec3 = glm::vec3;
using Vec4 = glm::vec4;
using IVec2 = glm::ivec2;
using IVec3 = glm::ivec3;
using IVec4 = glm::ivec4;
using Mat4 = glm::mat4;
} }

View File

@ -0,0 +1,16 @@
set(SRC_FILES
"imp/cpp/Device.cpp"
"imp/cpp/Buffer.cpp"
"imp/cpp/Texture.cpp"
"imp/cpp/Pipeline.cpp"
"imp/cpp/RenderCore.cpp"
"imp/cpp/TextureAtlas.cpp"
"imp/cpp/EmbeddedResources.cpp"
)
add_library(RenderCore STATIC ${SRC_FILES})
target_include_directories(RenderCore PUBLIC inc)
target_include_directories(RenderCore PRIVATE imp/hpp)
target_link_libraries(RenderCore PUBLIC IACore SDL3::SDL3 glm::glm)

View File

@ -10,7 +10,7 @@ layout(set = 2, binding = 0) uniform sampler2D texSampler;
void main() void main()
{ {
outColor = texture(texSampler, inTexCoord); outColor = texture(texSampler, inTexCoord) * inVertexColor;
if(outColor.w < 0.1) if(outColor.w < 0.1)
discard; discard;
} }

View File

@ -0,0 +1,124 @@
// 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 <https://www.gnu.org/licenses/>.
#include <RenderCore/Buffer.hpp>
namespace ia::iae
{
RDC_Buffer::RDC_Buffer() : m_type(EType::NONE), m_size(0)
{
}
RDC_Buffer::RDC_Buffer(IN EType type, IN UINT32 size) : m_type(type), m_size(size)
{
}
RDC_StagingBuffer::RDC_StagingBuffer(IN UINT32 size) : RDC_Buffer(EType::NONE, size)
{
SDL_GPUTransferBufferCreateInfo stagingBufferCreateInfo{.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = size};
m_buffer = SDL_CreateGPUTransferBuffer(RDC_Device::GetHandle(), &stagingBufferCreateInfo);
}
RDC_StagingBuffer::~RDC_StagingBuffer()
{
SDL_ReleaseGPUTransferBuffer(RDC_Device::GetHandle(), m_buffer);
}
VOID RDC_StagingBuffer::CopyFrom(IN PCVOID data, IN UINT32 size)
{
IA_ASSERT(size <= m_size);
const auto mappedPtr = SDL_MapGPUTransferBuffer(RDC_Device::GetHandle(), m_buffer, false);
SDL_memcpy(mappedPtr, data, size);
SDL_UnmapGPUTransferBuffer(RDC_Device::GetHandle(), m_buffer);
}
RDC_DeviceLocalBuffer::RDC_DeviceLocalBuffer()
{
}
RDC_DeviceLocalBuffer::RDC_DeviceLocalBuffer(IN RDC_Buffer::EType type, IN UINT32 size) : RDC_Buffer(type, size)
{
SDL_GPUBufferCreateInfo createInfo{.size = size};
switch (type)
{
case RDC_Buffer::EType::NONE:
THROW_INVALID_DATA();
break;
case RDC_Buffer::EType::VERTEX:
createInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
break;
case RDC_Buffer::EType::INDEX:
createInfo.usage = SDL_GPU_BUFFERUSAGE_INDEX;
break;
case RDC_Buffer::EType::STORAGE:
createInfo.usage = SDL_GPU_BUFFERUSAGE_GRAPHICS_STORAGE_READ;
break;
}
m_buffer = SDL_CreateGPUBuffer(RDC_Device::GetHandle(), &createInfo);
}
RDC_DeviceLocalBuffer::~RDC_DeviceLocalBuffer()
{
if (m_buffer)
SDL_ReleaseGPUBuffer(RDC_Device::GetHandle(), m_buffer);
}
VOID RDC_DeviceLocalBuffer::CopyFrom(IN RDC_StagingBuffer *stagingBuffer, IN UINT32 size)
{
CopyFrom(stagingBuffer->m_buffer, size);
}
VOID RDC_DeviceLocalBuffer::CopyFrom(IN SDL_GPUTransferBuffer *stagingBuffer, IN UINT32 size)
{
const auto cmdBuffer = SDL_AcquireGPUCommandBuffer(RDC_Device::GetHandle());
const auto copyPass = SDL_BeginGPUCopyPass(cmdBuffer);
SDL_GPUTransferBufferLocation src{.transfer_buffer = stagingBuffer, .offset = 0};
SDL_GPUBufferRegion dst{.buffer = m_buffer, .offset = 0, .size = size};
SDL_UploadToGPUBuffer(copyPass, &src, &dst, false);
SDL_EndGPUCopyPass(copyPass);
SDL_SubmitGPUCommandBuffer(cmdBuffer);
SDL_WaitForGPUIdle(RDC_Device::GetHandle());
}
RDC_HostVisibleBuffer::RDC_HostVisibleBuffer(IN RDC_Buffer::EType type, IN UINT32 size)
: RDC_DeviceLocalBuffer(type, size)
{
SDL_GPUTransferBufferCreateInfo stagingBufferCreateInfo{.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = size};
m_stagingBuffer = SDL_CreateGPUTransferBuffer(RDC_Device::GetHandle(), &stagingBufferCreateInfo);
}
RDC_HostVisibleBuffer::~RDC_HostVisibleBuffer()
{
SDL_ReleaseGPUTransferBuffer(RDC_Device::GetHandle(), m_stagingBuffer);
}
VOID RDC_HostVisibleBuffer::CopyFrom(IN PCVOID data, IN UINT32 size)
{
IA_ASSERT(size <= m_size);
const auto mappedPtr = SDL_MapGPUTransferBuffer(RDC_Device::GetHandle(), m_stagingBuffer, false);
SDL_memcpy(mappedPtr, data, size);
SDL_UnmapGPUTransferBuffer(RDC_Device::GetHandle(), m_stagingBuffer);
RDC_DeviceLocalBuffer::CopyFrom(m_stagingBuffer, size);
}
} // namespace ia::iae

View File

@ -0,0 +1,108 @@
// 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 <https://www.gnu.org/licenses/>.
#include <RenderCore/Buffer.hpp>
#include <RenderCore/Device.hpp>
namespace ia::iae
{
struct Geometry
{
INT32 IndexCount{};
RDC_DeviceLocalBuffer IndexBuffer;
RDC_DeviceLocalBuffer VertexBuffer;
};
} // namespace ia::iae
namespace ia::iae
{
SDL_GPUDevice *RDC_Device::s_handle{};
SDL_Window *RDC_Device::s_windowHandle{};
VOID RDC_Device::Initialize(IN SDL_Window *windowHandle, IN BOOL isDebugMode)
{
s_windowHandle = windowHandle;
SDL_PropertiesID deviceCreateProps = SDL_CreateProperties();
SDL_SetStringProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, nullptr);
SDL_SetBooleanProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_SPIRV_BOOLEAN, true);
SDL_SetBooleanProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_DEBUGMODE_BOOLEAN, isDebugMode);
SDL_SetBooleanProperty(deviceCreateProps, SDL_PROP_GPU_DEVICE_CREATE_FEATURE_DEPTH_CLAMPING_BOOLEAN, false);
if (!(s_handle = SDL_CreateGPUDeviceWithProperties(deviceCreateProps)))
THROW_UNKNOWN("Failed to create the SDL GPU Device: ", SDL_GetError());
SDL_DestroyProperties(deviceCreateProps);
if (!SDL_ClaimWindowForGPUDevice(s_handle, windowHandle))
THROW_UNKNOWN("Failed to initialize SDL GPU for the window: ", SDL_GetError());
SDL_SetGPUSwapchainParameters(s_handle, windowHandle, SDL_GPU_SWAPCHAINCOMPOSITION_SDR,
SDL_GPU_PRESENTMODE_VSYNC);
}
VOID RDC_Device::Terminate()
{
WaitForIdle();
SDL_ReleaseWindowFromGPUDevice(s_handle, s_windowHandle);
SDL_DestroyGPUDevice(s_handle);
}
VOID RDC_Device::WaitForIdle()
{
SDL_WaitForGPUIdle(s_handle);
}
Handle RDC_Device::CreateGeometry(IN CONST Vector<GeometryVertex> &vertices, IN CONST Vector<INT32> &indices)
{
const auto geometry = new Geometry();
const auto vertexDataSize = static_cast<UINT32>(vertices.size() * sizeof(vertices[0]));
const auto indexDataSize = static_cast<UINT32>(indices.size() * sizeof(indices[0]));
std::construct_at(&geometry->VertexBuffer, RDC_Buffer::EType::VERTEX, vertexDataSize);
std::construct_at(&geometry->IndexBuffer, RDC_Buffer::EType::INDEX, indexDataSize);
const auto stagingBuffer = new RDC_StagingBuffer(ia_max(vertexDataSize, indexDataSize));
stagingBuffer->CopyFrom(vertices.data(), vertexDataSize);
geometry->VertexBuffer.CopyFrom(stagingBuffer, vertexDataSize);
stagingBuffer->CopyFrom(indices.data(), indexDataSize);
geometry->IndexBuffer.CopyFrom(stagingBuffer, indexDataSize);
delete stagingBuffer;
geometry->IndexCount = static_cast<UINT32>(indices.size());
return (Handle) geometry;
}
VOID RDC_Device::DestroyGeometry(IN Handle _geometry)
{
const auto geometry = (Geometry *) _geometry;
delete geometry;
}
VOID RDC_Device::BindGeometry(IN SDL_GPURenderPass* renderPass, IN Handle _geometry)
{
const auto geometry = (Geometry *) _geometry;
SDL_GPUBufferBinding bufferBindings[] = {{.buffer = geometry->VertexBuffer.GetHandle(), .offset = 0},
{.buffer = geometry->IndexBuffer.GetHandle(), .offset = 0}};
SDL_BindGPUVertexBuffers(renderPass, 0, &bufferBindings[0], 1);
SDL_BindGPUIndexBuffer(renderPass, &bufferBindings[1], SDL_GPU_INDEXELEMENTSIZE_32BIT);
}
SDL_GPUTextureFormat RDC_Device::GetSwapchainTextureFormat()
{
return SDL_GetGPUSwapchainTextureFormat(s_handle, s_windowHandle);
}
} // namespace ia::iae

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,107 @@
// 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 <https://www.gnu.org/licenses/>.
#include <RenderCore/Pipeline.hpp>
namespace ia::iae
{
RDC_Pipeline::RDC_Pipeline(IN SDL_GPUTextureFormat renderTargetFormat, IN CONST StageDesc &vertexStageDesc,
IN CONST StageDesc &pixelStageDesc, IN BOOL enableVertexBuffer)
{
SDL_GPUShader *vertexShader{};
SDL_GPUShader *pixelShader{};
SDL_GPUShaderCreateInfo shaderCreateInfo = {
.entrypoint = "main",
.format = SDL_GPU_SHADERFORMAT_SPIRV,
.num_storage_textures = 0,
.num_storage_buffers = 0,
};
shaderCreateInfo.stage = SDL_GPU_SHADERSTAGE_VERTEX;
shaderCreateInfo.code = vertexStageDesc.SourceData.data();
shaderCreateInfo.code_size = vertexStageDesc.SourceData.size();
shaderCreateInfo.num_samplers = vertexStageDesc.SamplerCount;
shaderCreateInfo.num_uniform_buffers = vertexStageDesc.UniformBufferCount;
shaderCreateInfo.num_storage_buffers = vertexStageDesc.StorageBufferCount;
if (!(vertexShader = SDL_CreateGPUShader(RDC_Device::GetHandle(), &shaderCreateInfo)))
THROW_UNKNOWN("Failed to create a SDL shader: ", SDL_GetError());
shaderCreateInfo.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
shaderCreateInfo.code = pixelStageDesc.SourceData.data();
shaderCreateInfo.code_size = pixelStageDesc.SourceData.size();
shaderCreateInfo.num_samplers = pixelStageDesc.SamplerCount;
shaderCreateInfo.num_uniform_buffers = pixelStageDesc.UniformBufferCount;
shaderCreateInfo.num_storage_buffers = pixelStageDesc.StorageBufferCount;
if (!(pixelShader = SDL_CreateGPUShader(RDC_Device::GetHandle(), &shaderCreateInfo)))
THROW_UNKNOWN("Failed to create a SDL shader: ", SDL_GetError());
SDL_GPUColorTargetDescription colorTargetDesc = {
.format = renderTargetFormat,
.blend_state = {.src_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.color_blend_op = SDL_GPU_BLENDOP_ADD,
.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE,
.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA,
.alpha_blend_op = SDL_GPU_BLENDOP_ADD,
.enable_blend = true,
.enable_color_write_mask = false}};
SDL_GPUVertexBufferDescription vertexBufferDesc = {
.slot = 0,
.pitch = sizeof(GeometryVertex),
.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX,
.instance_step_rate = 0,
};
SDL_GPUVertexAttribute vertexAttributes[] = {
{.location = 0, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = 0},
{.location = 1, .buffer_slot = 0, .format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, .offset = sizeof(Vec2)}};
SDL_GPUGraphicsPipelineCreateInfo createInfo = {
.vertex_shader = vertexShader,
.fragment_shader = pixelShader,
.vertex_input_state = SDL_GPUVertexInputState{.vertex_buffer_descriptions = &vertexBufferDesc,
.num_vertex_buffers = enableVertexBuffer ? (UINT32) 1 : 0,
.vertex_attributes = vertexAttributes,
.num_vertex_attributes = enableVertexBuffer ? (UINT32) 2 : 0},
.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST,
.rasterizer_state = SDL_GPURasterizerState{.fill_mode = SDL_GPU_FILLMODE_FILL,
.cull_mode = SDL_GPU_CULLMODE_NONE,
.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE,
.enable_depth_clip = true},
.target_info = {.color_target_descriptions = &colorTargetDesc,
.num_color_targets = 1,
.has_depth_stencil_target = false},
};
if (!(m_handle = SDL_CreateGPUGraphicsPipeline(RDC_Device::GetHandle(), &createInfo)))
THROW_UNKNOWN("Failed to create a SDL graphics pipeline: ", SDL_GetError());
SDL_ReleaseGPUShader(RDC_Device::GetHandle(), pixelShader);
SDL_ReleaseGPUShader(RDC_Device::GetHandle(), vertexShader);
}
RDC_Pipeline::~RDC_Pipeline()
{
SDL_ReleaseGPUGraphicsPipeline(RDC_Device::GetHandle(), m_handle);
}
VOID RDC_Pipeline::Bind(IN SDL_GPURenderPass *renderPass)
{
SDL_BindGPUGraphicsPipeline(renderPass, m_handle);
}
} // namespace ia::iae

View File

@ -0,0 +1,292 @@
// 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 <https://www.gnu.org/licenses/>.
#include <RenderCore/RenderCore.hpp>
#include <RenderCore/Texture.hpp>
#include <RenderCore/TextureAtlas.hpp>
#include <EmbeddedResources.hpp>
namespace ia::iae
{
Mat4 RDC::s_viewMatrix;
Mat4 RDC::s_projectionMatrix;
SDL_Window *RDC::s_windowHandle;
Handle RDC::s_quadGeometry;
RDC_Pipeline *RDC::s_dynamicSpritePipeline;
SDL_GPUSampler *RDC::s_linearClampSampler;
SDL_GPUSampler *RDC::s_linearRepeatSampler;
Vec2 RDC::s_cameraPosition{};
IVec2 RDC::s_viewportExtent;
RDC_TextureAtlas *RDC::s_staticSpriteAtlas{};
RDC_TextureAtlas *RDC::s_dynamicSpriteAtlas{};
INT32 RDC::s_spriteInstanceCount{};
RDC_HostVisibleBuffer *RDC::s_staticSpriteInstanceBuffer{};
RDC_HostVisibleBuffer *RDC::s_dynamicSpriteInstanceBuffer{};
RDC_Texture *RDC::s_defaultTexture{};
RDC_SpriteInstanceData RDC::s_spriteInstances[RDC::MAX_SPRITE_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_dynamicSpritePipeline;
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::RenderToWindow()
{
STATIC SDL_GPURenderPass *ActiveRenderPass{};
STATIC SDL_GPUCommandBuffer *ActiveCommandBuffer{};
STATIC SDL_GPUColorTargetInfo ActiveColorTargetInfo{.clear_color = SDL_FColor{0.0f, 0.0f, 0.0f, 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;
ActiveColorTargetInfo.clear_color = SDL_FColor{0.3f, 0.3f, 0.3f, 1.0f};
ActiveRenderPass = SDL_BeginGPURenderPass(ActiveCommandBuffer, &ActiveColorTargetInfo, 1, nullptr);
s_dynamicSpritePipeline->Bind(ActiveRenderPass);
SDL_PushGPUVertexUniformData(ActiveCommandBuffer, 0, &s_projectionMatrix, sizeof(Mat4));
SDL_PushGPUVertexUniformData(ActiveCommandBuffer, 1, &s_viewMatrix, sizeof(Mat4));
SDL_GPUTextureSamplerBinding textureBinding{.texture = s_dynamicSpriteAtlas
? s_dynamicSpriteAtlas->GetTexture()->GetHandle()
: s_defaultTexture->GetHandle(),
.sampler = s_linearRepeatSampler};
SDL_BindGPUFragmentSamplers(ActiveRenderPass, 0, &textureBinding, 1);
RDC_Device::BindGeometry(ActiveRenderPass, s_quadGeometry);
if (s_spriteInstanceCount)
{
const auto spriteInstanceBuffer = s_dynamicSpriteInstanceBuffer->GetHandle();
s_dynamicSpriteInstanceBuffer->CopyFrom(s_spriteInstances,
sizeof(RDC_SpriteInstanceData) * s_spriteInstanceCount);
SDL_BindGPUVertexStorageBuffers(ActiveRenderPass, 0, &spriteInstanceBuffer, 1);
SDL_DrawGPUIndexedPrimitives(ActiveRenderPass, 6, s_spriteInstanceCount, 0, 0, 0);
}
s_spriteInstanceCount = 0;
SDL_EndGPURenderPass(ActiveRenderPass);
SDL_SubmitGPUCommandBuffer(ActiveCommandBuffer);
}
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;
}
} // 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<Handle> &images)
{
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));
}
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_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,
},
true);
}
VOID RDC::InitializeGeometries()
{
s_quadGeometry = RDC_Device::CreateGeometry(
{
{glm::vec2{0, 1}, glm::vec2{0, 1}},
{glm::vec2{1, 1}, glm::vec2{1, 1}},
{glm::vec2{1, 0}, glm::vec2{1, 0}},
{glm::vec2{0, 0}, glm::vec2{0, 0}},
},
{0, 1, 2, 2, 3, 0});
}
} // namespace ia::iae

View File

@ -0,0 +1,121 @@
// 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 <https://www.gnu.org/licenses/>.
#include <RenderCore/Texture.hpp>
namespace ia::iae
{
RDC_Texture::RDC_Texture(IN EType type, IN INT32 width, IN INT32 height, IN BOOL generateMipMaps)
: m_type(type), m_width(width), m_height(height),
m_mipLevels(generateMipMaps ? ia_max((UINT32) (floor(log2(ia_max(width, height))) + 1), (UINT32) 1)
: (UINT32) 1)
{
SDL_GPUTextureCreateInfo createInfo{.type = SDL_GPU_TEXTURETYPE_2D,
.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM,
.width = (UINT32) width,
.height = (UINT32) height,
.layer_count_or_depth = 1,
.num_levels = m_mipLevels,
.sample_count = SDL_GPU_SAMPLECOUNT_1};
switch (type)
{
case EType::SAMPLED:
createInfo.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
break;
case EType::RENDER_TARGET:
createInfo.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
break;
}
m_handle = SDL_CreateGPUTexture(RDC_Device::GetHandle(), &createInfo);
if (!m_handle)
THROW_UNKNOWN("Failed to create a SDL GPU Texture: ", SDL_GetError());
}
RDC_Texture::~RDC_Texture()
{
SDL_ReleaseGPUTexture(RDC_Device::GetHandle(), m_handle);
}
VOID RDC_Texture::SetImageData(IN PCUINT8 rgbaData, IN INT32 stride)
{
STATIC Vector<UINT8> TMP_COLOR_BUFFER;
TMP_COLOR_BUFFER.resize(m_width * m_height * 4);
if (stride == -1)
{
for (SIZE_T i = 0; i < TMP_COLOR_BUFFER.size() >> 2; i++)
{
const auto a = static_cast<FLOAT32>(rgbaData[i * 4 + 3]) / 255.0f;
TMP_COLOR_BUFFER[i * 4 + 0] = static_cast<UINT8>(rgbaData[i * 4 + 0] * a);
TMP_COLOR_BUFFER[i * 4 + 1] = static_cast<UINT8>(rgbaData[i * 4 + 1] * a);
TMP_COLOR_BUFFER[i * 4 + 2] = static_cast<UINT8>(rgbaData[i * 4 + 2] * a);
TMP_COLOR_BUFFER[i * 4 + 3] = rgbaData[i * 4 + 3];
}
}
else
{
for (INT32 y = 0; y < m_height; y++)
{
for (INT32 x = 0; x < m_width; x++)
{
const auto p = &rgbaData[(x + y * stride) * 4];
const auto a = static_cast<FLOAT32>(p[3]) / 255.0f;
TMP_COLOR_BUFFER[(x + y * m_width) * 4 + 0] = static_cast<UINT8>(p[0] * a);
TMP_COLOR_BUFFER[(x + y * m_width) * 4 + 1] = static_cast<UINT8>(p[1] * a);
TMP_COLOR_BUFFER[(x + y * m_width) * 4 + 2] = static_cast<UINT8>(p[2] * a);
TMP_COLOR_BUFFER[(x + y * m_width) * 4 + 3] = p[3];
}
}
}
SDL_GPUTransferBufferCreateInfo stagingBufferCreateInfo{.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD,
.size = (UINT32) m_width * (UINT32) m_height * 4};
const auto stagingBuffer = SDL_CreateGPUTransferBuffer(RDC_Device::GetHandle(), &stagingBufferCreateInfo);
const auto mappedPtr = SDL_MapGPUTransferBuffer(RDC_Device::GetHandle(), stagingBuffer, false);
SDL_memcpy(mappedPtr, TMP_COLOR_BUFFER.data(), m_width * m_height * 4);
SDL_UnmapGPUTransferBuffer(RDC_Device::GetHandle(), stagingBuffer);
auto cmdBuffer = SDL_AcquireGPUCommandBuffer(RDC_Device::GetHandle());
const auto copyPass = SDL_BeginGPUCopyPass(cmdBuffer);
SDL_GPUTextureTransferInfo transferInfo{.transfer_buffer = stagingBuffer, .offset = 0};
SDL_GPUTextureRegion region{.texture = m_handle, .w = (UINT32) m_width, .h = (UINT32) m_height, .d = 1};
SDL_UploadToGPUTexture(copyPass, &transferInfo, &region, false);
SDL_EndGPUCopyPass(copyPass);
SDL_SubmitGPUCommandBuffer(cmdBuffer);
SDL_WaitForGPUIdle(RDC_Device::GetHandle());
SDL_ReleaseGPUTransferBuffer(RDC_Device::GetHandle(), stagingBuffer);
if (m_mipLevels > 1)
{
cmdBuffer = SDL_AcquireGPUCommandBuffer(RDC_Device::GetHandle());
SDL_GenerateMipmapsForGPUTexture(cmdBuffer, m_handle);
SDL_SubmitGPUCommandBuffer(cmdBuffer);
SDL_WaitForGPUIdle(RDC_Device::GetHandle());
}
}
VOID RDC_Texture::BindAsSampler(IN SDL_GPURenderPass *renderPass, IN INT32 index, IN SDL_GPUSampler *sampler)
{
SDL_GPUTextureSamplerBinding textureBinding{.texture = m_handle, .sampler = sampler};
SDL_BindGPUFragmentSamplers(renderPass, index, &textureBinding, 1);
}
} // namespace ia::iae

View File

@ -0,0 +1,82 @@
// 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 <https://www.gnu.org/licenses/>.
#include <RenderCore/TextureAtlas.hpp>
namespace ia::iae
{
RDC_TextureAtlas::RDC_TextureAtlas(IN CONST Vector<Handle> &images)
{
if (images.empty())
return;
m_atlasSize.x = 0;
m_atlasSize.y = 0;
for (const auto &_image : images)
{
const auto d = (ImageData *) _image;
m_atlasSize.x += d->Width;
if (d->Height > m_atlasSize.y)
m_atlasSize.y = d->Height;
}
m_inverseAtlasSize = {1.0f / ((FLOAT32) m_atlasSize.x), 1.0f / ((FLOAT32) m_atlasSize.y)};
const auto pixels = new UINT8[m_atlasSize.x * m_atlasSize.y * 4];
INT32 atlasCursor{0};
for (const auto &_image : images)
{
const auto d = (ImageData *) _image;
for (INT32 y = 0; y < d->Height; y++)
ia_memcpy(&pixels[(atlasCursor + (y * m_atlasSize.x)) * 4], &d->Pixels[y * d->Width * 4], d->Width * 4);
m_texCoordMap[_image] = Vec2(((FLOAT32) atlasCursor) / ((FLOAT32) m_atlasSize.x), 0.0f);
atlasCursor += d->Width;
}
m_texture = new RDC_Texture(RDC_Texture::EType::SAMPLED, m_atlasSize.x, m_atlasSize.y);
m_texture->SetImageData(pixels);
delete[] pixels;
}
RDC_TextureAtlas::~RDC_TextureAtlas()
{
delete m_texture;
}
Vec4 RDC_TextureAtlas::GetTextureCoordinates(IN Handle _image, IN INT32 tileIndexX, IN INT32 tileIndexY, IN BOOL flipH,
IN BOOL flipV, IN Vec2 uvOffset)
{
const auto d = (ImageData *) _image;
const auto &t = m_texCoordMap[_image];
const auto pX = ((tileIndexX + uvOffset.x) * ((FLOAT32) d->TileWidth)) * m_inverseAtlasSize.x;
const auto pY = ((tileIndexY + uvOffset.y) * ((FLOAT32) d->TileHeight)) * m_inverseAtlasSize.y;
auto texCoords = Vec4(t.x + pX, t.y + pY, d->TileWidth * m_inverseAtlasSize.x,
d->TileHeight * m_inverseAtlasSize.y);
if (flipH)
{
texCoords.x += texCoords.z;
texCoords.z *= -1;
}
if (flipV)
{
texCoords.y += texCoords.w;
texCoords.w *= -1;
}
return texCoords;
}
} // namespace ia::iae

View File

@ -16,7 +16,7 @@
#pragma once #pragma once
#include <IAEngine/Base.hpp> #include <RenderCore/Base.hpp>
namespace ia::iae namespace ia::iae
{ {

View File

@ -0,0 +1,64 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <IACore/Exception.hpp>
#include <IACore/Logger.hpp>
#include <IACore/Map.hpp>
#include <IACore/Memory.hpp>
#include <IACore/String.hpp>
#include <IACore/Vector.hpp>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/ext/scalar_constants.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#define RDC_LOG_TAG "RDC"
#define RDC_LOG_INFO(...) ia::Logger::Info(RDC_LOG_TAG, __VA_ARGS__)
#define RDC_LOG_WARN(...) ia::Logger::Warn(RDC_LOG_TAG, __VA_ARGS__)
#define RDC_LOG_ERROR(...) ia::Logger::Error(RDC_LOG_TAG, __VA_ARGS__)
#define RDC_LOG_SUCCESS(...) ia::Logger::Success(RDC_LOG_TAG, __VA_ARGS__)
namespace ia::iae
{
using Handle = INT64;
STATIC CONSTEXPR Handle INVALID_HANDLE = -1;
using Vec2 = glm::vec2;
using Vec3 = glm::vec3;
using Vec4 = glm::vec4;
using IVec2 = glm::ivec2;
using IVec3 = glm::ivec3;
using IVec4 = glm::ivec4;
using Mat4 = glm::mat4;
struct ImageData
{
PUINT8 Pixels{};
INT32 Width{};
INT32 Height{};
INT32 TileWidth{};
INT32 TileHeight{};
INT32 TileCountX{};
INT32 TileCountY{};
};
} // namespace ia::iae

View File

@ -0,0 +1,90 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <RenderCore/Device.hpp>
namespace ia::iae
{
class RDC_StagingBuffer;
class RDC_Buffer
{
public:
enum class EType
{
NONE,
VERTEX,
INDEX,
STORAGE
};
public:
RDC_Buffer();
RDC_Buffer(IN EType type, IN UINT32 size);
protected:
CONST EType m_type;
CONST UINT32 m_size;
};
class RDC_DeviceLocalBuffer : public RDC_Buffer
{
public:
RDC_DeviceLocalBuffer();
RDC_DeviceLocalBuffer(IN RDC_Buffer::EType type, IN UINT32 size);
~RDC_DeviceLocalBuffer();
VOID CopyFrom(IN RDC_StagingBuffer *stagingBuffer, IN UINT32 size);
VOID CopyFrom(IN SDL_GPUTransferBuffer *stagingBuffer, IN UINT32 size);
SDL_GPUBuffer *GetHandle()
{
return m_buffer;
}
protected:
SDL_GPUBuffer *m_buffer{};
};
class RDC_HostVisibleBuffer : public RDC_DeviceLocalBuffer
{
public:
RDC_HostVisibleBuffer(IN RDC_Buffer::EType type, IN UINT32 size);
~RDC_HostVisibleBuffer();
VOID CopyFrom(IN PCVOID data, IN UINT32 size);
private:
SDL_GPUTransferBuffer *m_stagingBuffer{};
};
class RDC_StagingBuffer : public RDC_Buffer
{
public:
RDC_StagingBuffer(IN UINT32 size);
~RDC_StagingBuffer();
VOID CopyFrom(IN PCVOID data, IN UINT32 size);
private:
SDL_GPUTransferBuffer *m_buffer{};
friend class RDC_DeviceLocalBuffer;
};
} // namespace ia::iae

View File

@ -0,0 +1,56 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <RenderCore/Base.hpp>
#include <SDL3/SDL.h>
namespace ia::iae
{
struct GeometryVertex
{
Vec2 Position;
Vec2 TexCoords;
};
class RDC_Device
{
public:
STATIC VOID Initialize(IN SDL_Window *windowHandle, IN BOOL isDebugMode);
STATIC VOID Terminate();
public:
STATIC VOID WaitForIdle();
STATIC Handle CreateGeometry(IN CONST Vector<GeometryVertex> &vertices, IN CONST Vector<INT32> &indices);
STATIC VOID DestroyGeometry(IN Handle geometry);
STATIC VOID BindGeometry(IN SDL_GPURenderPass* renderPass, IN Handle geometry);
public:
STATIC SDL_GPUTextureFormat GetSwapchainTextureFormat();
public:
STATIC SDL_GPUDevice *GetHandle()
{
return s_handle;
}
private:
STATIC SDL_GPUDevice *s_handle;
STATIC SDL_Window *s_windowHandle;
};
} // namespace ia::iae

View File

@ -0,0 +1,50 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <RenderCore/Device.hpp>
namespace ia::iae
{
class RDC_Pipeline
{
public:
struct StageDesc
{
Vector<UINT8> SourceData{};
UINT32 SamplerCount{};
UINT32 UniformBufferCount{};
UINT32 StorageBufferCount{};
};
public:
RDC_Pipeline(IN SDL_GPUTextureFormat renderTargetFormat, IN CONST StageDesc &vertexStageDesc, IN CONST StageDesc &pixelStageDesc,
IN BOOL enableVertexBuffer);
~RDC_Pipeline();
VOID Bind(IN SDL_GPURenderPass* renderPass);
public:
SDL_GPUGraphicsPipeline *GetHandle() CONST
{
return m_handle;
}
private:
SDL_GPUGraphicsPipeline *m_handle{};
};
} // namespace ia::iae

View File

@ -0,0 +1,90 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <RenderCore/Buffer.hpp>
#include <RenderCore/Texture.hpp>
#include <RenderCore/Pipeline.hpp>
#include <RenderCore/TextureAtlas.hpp>
namespace ia::iae
{
#pragma pack(push, 1)
struct RDC_SpriteInstanceData
{
Mat4 Transform{1.0f};
Vec4 TexCoords{};
Vec4 Color{1.0f, 1.0f, 1.0f, 1.0f};
};
#pragma pack(pop)
class RDC
{
public:
STATIC CONSTEXPR INT32 MAX_SPRITE_COUNT = 100000;
public:
STATIC VOID Initialize(IN IVec2 viewportExtent, IN SDL_Window *windowHandle, IN BOOL isDebugMode);
STATIC VOID Terminate();
STATIC VOID ResizeScreen(IN IVec2 newExtent);
STATIC VOID RenderToWindow();
public:
STATIC Vec2 GetCameraPosition();
STATIC VOID SetCameraPosition(IN Vec2 position);
STATIC Vec2 DrawSpriteTopLeft(IN Handle image, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Vec2 DrawSpriteCentered(IN Handle image, IN INT32 tileIndexX, IN INT32 tileIndexY,
IN Vec2 position, IN Vec2 scale, IN FLOAT32 rotation, IN BOOL flipH = false, IN BOOL flipV = false, IN Vec2 uvOffset = {});
STATIC Handle CreateImage(IN PCUINT8 rgbaData, IN INT32 width, IN INT32 height, IN INT32 tileCountX = 1,
IN INT32 tileCountY = 1);
STATIC VOID DestroyImage(IN Handle image);
STATIC VOID CompileTextures(IN CONST Vector<Handle>& images);
private:
STATIC VOID InitializeSamplers();
STATIC VOID InitializeDrawData();
STATIC VOID InitializeTextures();
STATIC VOID InitializePipelines();
STATIC VOID InitializeGeometries();
private:
STATIC Mat4 s_viewMatrix;
STATIC IVec2 s_viewportExtent;
STATIC Mat4 s_projectionMatrix;
STATIC Vec2 s_cameraPosition;
STATIC Handle s_quadGeometry;
STATIC SDL_Window *s_windowHandle;
STATIC RDC_Pipeline *s_dynamicSpritePipeline;
STATIC SDL_GPUSampler *s_linearClampSampler;
STATIC SDL_GPUSampler *s_linearRepeatSampler;
STATIC RDC_TextureAtlas *s_staticSpriteAtlas;
STATIC RDC_TextureAtlas *s_dynamicSpriteAtlas;
STATIC RDC_HostVisibleBuffer* s_staticSpriteInstanceBuffer;
STATIC RDC_HostVisibleBuffer* s_dynamicSpriteInstanceBuffer;
STATIC INT32 s_spriteInstanceCount;
STATIC RDC_Texture* s_defaultTexture;
STATIC RDC_SpriteInstanceData s_spriteInstances[MAX_SPRITE_COUNT];
};
} // namespace ia::iae

View File

@ -0,0 +1,63 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <RenderCore/Device.hpp>
namespace ia::iae
{
class RDC_Texture
{
public:
enum class EType
{
SAMPLED,
RENDER_TARGET
};
public:
RDC_Texture(IN EType type, IN INT32 width, IN INT32 height, IN BOOL generateMipMaps = false);
~RDC_Texture();
VOID SetImageData(IN PCUINT8 rgbaData, IN INT32 stride = -1);
VOID BindAsSampler(IN SDL_GPURenderPass* renderPass, IN INT32 index, IN SDL_GPUSampler* sampler);
public:
SDL_GPUTexture *GetHandle() CONST
{
return m_handle;
}
INT32 Width() CONST
{
return m_width;
}
INT32 Height() CONST
{
return m_height;
}
private:
CONST EType m_type;
CONST INT32 m_width;
CONST INT32 m_height;
CONST UINT32 m_mipLevels;
SDL_GPUTexture *m_handle;
};
} // namespace ia::iae

View File

@ -0,0 +1,44 @@
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include <RenderCore/Texture.hpp>
namespace ia::iae
{
class RDC_TextureAtlas
{
public:
RDC_TextureAtlas(IN CONST Vector<Handle> &images);
~RDC_TextureAtlas();
Vec4 GetTextureCoordinates(IN Handle image, IN INT32 tileIndexX, IN INT32 tileIndexY, IN BOOL flipH,
IN BOOL flipV, IN Vec2 uvOffset);
public:
CONST RDC_Texture *GetTexture() CONST
{
return m_texture;
}
private:
IVec2 m_atlasSize;
RDC_Texture *m_texture;
Vec2 m_inverseAtlasSize;
Map<Handle, Vec2> m_texCoordMap;
};
} // namespace ia::iae

View File

@ -30,10 +30,10 @@ class Resource:
self.data = b'' self.data = b''
self.Type = "" # Binary, Text, Shader self.Type = "" # Binary, Text, Shader
def load_resources() -> list[Resource]: def load_resources(projectName: str) -> list[Resource]:
result = [] result = []
imagePaths = glob.glob("Resources/Images/*.*", recursive=True) imagePaths = glob.glob(f"Src/{projectName}/Resources/Images/*.*", recursive=True)
for t in imagePaths: for t in imagePaths:
r = Resource() r = Resource()
r.path = t.replace('\\', '/') r.path = t.replace('\\', '/')
@ -44,7 +44,7 @@ def load_resources() -> list[Resource]:
r.data = f.read() r.data = f.read()
result.append(r) result.append(r)
fontPaths = glob.glob("Resources/Fonts/*.ttf", recursive=True) fontPaths = glob.glob(f"Src/{projectName}/Resources/Fonts/*.ttf", recursive=True)
for t in fontPaths: for t in fontPaths:
r = Resource() r = Resource()
r.path = t.replace('\\', '/') r.path = t.replace('\\', '/')
@ -55,7 +55,7 @@ def load_resources() -> list[Resource]:
r.data = f.read() r.data = f.read()
result.append(r) result.append(r)
shaderPaths = glob.glob("Resources/Shaders/*.*", recursive=True) shaderPaths = glob.glob(f"Src/{projectName}/Resources/Shaders/*.*", recursive=True)
for t in shaderPaths: for t in shaderPaths:
r = Resource() r = Resource()
r.path = t.replace('\\', '/') r.path = t.replace('\\', '/')
@ -70,10 +70,14 @@ def load_resources() -> list[Resource]:
return result return result
def main(): def main(args: list[str]):
resoruces = load_resources() if len(args) < 2:
print(f"\033[33mUsage: {args[0]} <ProjectName>\033[39m")
return
add_header_file("EmbeddedResources.hpp", "Src/IAEngine/imp/hpp", """ class EmbeddedResources resoruces = load_resources(args[1])
add_header_file("EmbeddedResources.hpp", f"Src/{args[1]}/imp/hpp", """ class EmbeddedResources
{ {
public: public:
STATIC VOID Initialize(); STATIC VOID Initialize();
@ -99,7 +103,7 @@ def main():
resourceInit.append(f"\t\ts_resources[\"{r.path}\"] = DATA_{r.name};") resourceInit.append(f"\t\ts_resources[\"{r.path}\"] = DATA_{r.name};")
add_source_file("EmbeddedResources.cpp", "Src/IAEngine/imp/cpp", f"""#include <EmbeddedResources.hpp> add_source_file("EmbeddedResources.cpp", f"Src/{args[1]}/imp/cpp", f"""#include <EmbeddedResources.hpp>
namespace ia::iae namespace ia::iae
{{ {{
@ -129,4 +133,4 @@ namespace ia::iae
""") """)
if __name__ == "__main__": if __name__ == "__main__":
main() main(sys.argv)

View File

@ -17,12 +17,6 @@ add_subdirectory(SDL/)
set(SDLMIXER_VENDORED OFF) set(SDLMIXER_VENDORED OFF)
add_subdirectory(SDL_mixer/) add_subdirectory(SDL_mixer/)
# -----------------------------------------------
# FreeType
# -----------------------------------------------
add_subdirectory(freetype/)
add_library(Freetype::Freetype ALIAS freetype)
# ----------------------------------------------- # -----------------------------------------------
# zlib # zlib
# ----------------------------------------------- # -----------------------------------------------

1
Vendor/freetype vendored

Submodule Vendor/freetype deleted from ae63cc0d13