Moved UI rendering to a separate class/file.

This commit is contained in:
Patrick 2024-09-17 11:59:14 +02:00
parent 773d86ffd3
commit 0168ade08c
6 changed files with 230 additions and 151 deletions

View File

@ -9,9 +9,7 @@
#include "../sdlpp/keyboard.hpp"
#include "../util/bitmap.hpp"
#include "../util/font_map.hpp"
#include "../util/mesh.hpp"
#include "../util/texture_atlas.hpp"
namespace sdl_gpu_test
{
@ -26,11 +24,6 @@ struct VertexShaderParameters
glm::mat4 worldToView;
glm::mat4 viewToClip;
};
struct UIVertexShaderParameters
{
glm::vec2 windowSize;
};
}
void UIApp::init(const AppInitArgs& args)
@ -49,7 +42,7 @@ void UIApp::init(const AppInitArgs& args)
createMeshPipeline();
// create UI pipeline
createUIPipeline();
mUIRenderer.init(*this);
// load the mesh
const Mesh mesh = loadMesh(mFileSystem.getPath("meshes/cube.obj"));
@ -70,57 +63,6 @@ void UIApp::init(const AppInitArgs& args)
mTexture = loadTexture("bitmaps/cube.png", textureArgs);
mSampler.create(mDevice, {});
const FontMap fontMap = loadFontMap(mFileSystem.getPath("fonts/symtext.fnt"));
const UVFontMap uvFontMap = makeUVFontMap({
.original = &fontMap,
.textureWidth = 256,
.textureHeight = 256
});
unsigned posX = 100;
const unsigned posY = 100;
for (const char chr : std::string_view("Dies ist ein Test-Text!"))
{
const UVFontMapEntry& entry = uvFontMap.entries[chr < 0 ? '_' : chr];
const UIVertex topLeft = {
.pos = {posX + entry.xOffset, posY + entry.yOffset},
.texcoord = {entry.uvX, entry.uvY}
};
const UIVertex bottomRight = {
.pos = {topLeft.pos.x + static_cast<float>(entry.width), topLeft.pos.y + static_cast<float>(entry.height)},
.texcoord = {entry.uvX + entry.uvWidth, entry.uvY + entry.uvHeight}
};
const UIVertex bottomLeft = {
.pos = {topLeft.pos.x, bottomRight.pos.y},
.texcoord = {topLeft.texcoord.x, bottomRight.texcoord.y}
};
const UIVertex topRight = {
.pos = {bottomRight.pos.x, topLeft.pos.y},
.texcoord = {bottomRight.texcoord.x, topLeft.texcoord.y}
};
mUIVertices.push_back(topLeft);
mUIVertices.push_back(bottomLeft);
mUIVertices.push_back(topRight);
mUIVertices.push_back(bottomRight);
mUIVertices.push_back(topRight);
mUIVertices.push_back(bottomLeft);
posX += entry.xAdvance;
}
// create UI vertex buffer
mUIVertexBuffer.create(mDevice, {
.usage = {.vertex = true},
.size = static_cast<Uint32>(mUIVertices.size() * sizeof(UIVertex))
});
uploadVertexData(mUIVertexBuffer, std::span(mUIVertices));
// and UI texture
mUITexture = loadTexture("bitmaps/ui.png", textureArgs);
mUISampler.create(mDevice, {});
// open gamepad
const std::vector<SDL_JoystickID> gamepads = sdlpp::getGamepads();
if (!gamepads.empty())
@ -188,25 +130,12 @@ void UIApp::update(const AppUpdateArgs& args)
renderPass.end();
// render the "UI"
if (!mUIVertices.empty())
{
const UIVertexShaderParameters uiVertexShaderParameters = {
.windowSize = {
static_cast<float>(swapchainWidth), static_cast<float>(swapchainHeight)
}
};
colorTargets[0].loadOp = sdlpp::GPULoadOp::LOAD; // don't clear again
renderPass = cmdBuffer.beginRenderPass({
.colorTargetInfos = colorTargets
mUIRenderer.render({
.cmdBuffer = &cmdBuffer,
.targetTexture = &swapchainTexture,
.targetTextureWidth = swapchainWidth,
.targetTextureHeight = swapchainHeight
});
cmdBuffer.pushFragmentUniformData(0, std::span(&WHITE, 1));
cmdBuffer.pushVertexUniformData(0, std::span(&uiVertexShaderParameters, 1));
renderPass.bindFragmentSampler({.texture = mUITexture, .sampler = mUISampler});
renderPass.bindGraphicsPipeline(mUIPipeline);
renderPass.bindVertexBuffer({.buffer = mUIVertexBuffer});
renderPass.drawPrimitives({.numVertices = static_cast<Uint32>(mUIVertices.size())});
renderPass.end();
}
// finalize
cmdBuffer.submit();
@ -300,67 +229,6 @@ void UIApp::createMeshPipeline()
});
}
void UIApp::createUIPipeline()
{
// create shaders
const sdlpp::GPUShader vertexShader = loadShader("shaders/glsl/ui.vert.spv", {
.format = sdlpp::GPUShaderFormat::SPIRV,
.stage = sdlpp::GPUShaderStage::VERTEX,
.numUniformBuffers = 1
});
const sdlpp::GPUShader fragmentShader = loadShader("shaders/glsl/color_from_texture.frag.spv", {
.format = sdlpp::GPUShaderFormat::SPIRV,
.stage = sdlpp::GPUShaderStage::FRAGMENT,
.numSamplers = 1,
.numUniformBuffers = 1
});
std::array colorTargetsDescs = {
sdlpp::GPUColorTargetDescription{
.format = mDevice.getSwapchainTextureFormat(mWindow),
.blendState = {
.enableBlend = true,
.srcColorBlendfactor = sdlpp::GPUBlendFactor::SRC_ALPHA,
.dstColorBlendfactor = sdlpp::GPUBlendFactor::ONE_MINUS_SRC_ALPHA,
.colorBlendOp = sdlpp::GPUBlendOp::ADD,
.srcAlphaBlendfactor = sdlpp::GPUBlendFactor::ONE,
.dstAlphaBlendfactor = sdlpp::GPUBlendFactor::ZERO,
.alphaBlendOp = sdlpp::GPUBlendOp::ADD
}
}
};
std::array vertexBindings = {
sdlpp::GPUVertexBinding{
.index = 0,
.pitch = sizeof(UIVertex)
}
};
std::array vertexAttributes = {
sdlpp::GPUVertexAttribute{
.location = 0,
.bindingIndex = 0,
.format = sdlpp::GPUVertexElementFormat::FLOAT2,
.offset = offsetof(UIVertex, pos)
},
sdlpp::GPUVertexAttribute{
.location = 1,
.bindingIndex = 0,
.format = sdlpp::GPUVertexElementFormat::FLOAT2,
.offset = offsetof(UIVertex, texcoord)
}
};
mUIPipeline.create(mDevice, {
.vertexShader = vertexShader,
.fragmentShader = fragmentShader,
.vertexInputState = {
.vertexBindings = vertexBindings,
.vertexAttributes = vertexAttributes
},
.targetInfo = {
.colorTargetDescriptions = colorTargetsDescs
}
});
}
void UIApp::processInput(const AppUpdateArgs& args)
{
const std::span<const SDL_bool> keystates = sdlpp::getKeyboardState();

View File

@ -6,17 +6,12 @@
#include <glm/vec2.hpp>
#include "./ui/ui_renderer.hpp"
#include "../application.hpp"
#include "../sdlpp/gamepad.hpp"
namespace sdl_gpu_test
{
struct UIVertex
{
glm::vec2 pos;
glm::vec2 texcoord;
};
class UIApp : public Application
{
private:
@ -26,11 +21,7 @@ private:
sdlpp::GPUTexture mTexture;
sdlpp::GPUSampler mSampler;
sdlpp::GPUBuffer mUIVertexBuffer;
sdlpp::GPUGraphicsPipeline mUIPipeline;
std::vector<UIVertex> mUIVertices;
sdlpp::GPUTexture mUITexture;
sdlpp::GPUSampler mUISampler;
UIRenderer mUIRenderer;
sdlpp::Gamepad mGamepad;
Uint32 mNumVertices = 0;
@ -45,7 +36,6 @@ public:
void handleMouseMotionEvent(const sdlpp::MouseMotionEvent& event) override;
private:
void createMeshPipeline();
void createUIPipeline();
void processInput(const AppUpdateArgs& args);
};
} // namespace sdl_gpu_test

View File

@ -0,0 +1,167 @@
#include "./ui_renderer.hpp"
#include <glm/vec4.hpp>
namespace sdl_gpu_test::inline app6
{
namespace
{
struct UIVertexShaderParameters
{
glm::vec2 windowSize;
};
}
void UIRenderer::init(Application& application)
{
// for convenience
mApplication = &application;
const sdlpp::GPUDevice& device = application.getDevice();
// create shaders
const sdlpp::GPUShader vertexShader = application.loadShader("shaders/glsl/ui.vert.spv", {
.format = sdlpp::GPUShaderFormat::SPIRV,
.stage = sdlpp::GPUShaderStage::VERTEX,
.numUniformBuffers = 1
});
const sdlpp::GPUShader fragmentShader = application.loadShader("shaders/glsl/color_from_texture.frag.spv", {
.format = sdlpp::GPUShaderFormat::SPIRV,
.stage = sdlpp::GPUShaderStage::FRAGMENT,
.numSamplers = 1,
.numUniformBuffers = 1
});
std::array colorTargetsDescs = {
sdlpp::GPUColorTargetDescription{
.format = device.getSwapchainTextureFormat(application.getWindow()),
.blendState = {
.enableBlend = true,
.srcColorBlendfactor = sdlpp::GPUBlendFactor::SRC_ALPHA,
.dstColorBlendfactor = sdlpp::GPUBlendFactor::ONE_MINUS_SRC_ALPHA,
.colorBlendOp = sdlpp::GPUBlendOp::ADD,
.srcAlphaBlendfactor = sdlpp::GPUBlendFactor::ONE,
.dstAlphaBlendfactor = sdlpp::GPUBlendFactor::ZERO,
.alphaBlendOp = sdlpp::GPUBlendOp::ADD
}
}
};
std::array vertexBindings = {
sdlpp::GPUVertexBinding{
.index = 0,
.pitch = sizeof(UIVertex)
}
};
std::array vertexAttributes = {
sdlpp::GPUVertexAttribute{
.location = 0,
.bindingIndex = 0,
.format = sdlpp::GPUVertexElementFormat::FLOAT2,
.offset = offsetof(UIVertex, pos)
},
sdlpp::GPUVertexAttribute{
.location = 1,
.bindingIndex = 0,
.format = sdlpp::GPUVertexElementFormat::FLOAT2,
.offset = offsetof(UIVertex, texcoord)
}
};
mUIPipeline.create(device, {
.vertexShader = vertexShader,
.fragmentShader = fragmentShader,
.vertexInputState = {
.vertexBindings = vertexBindings,
.vertexAttributes = vertexAttributes
},
.targetInfo = {
.colorTargetDescriptions = colorTargetsDescs
}
});
const FontMap fontMap = loadFontMap(application.getFileSystem().getPath("fonts/symtext.fnt"));
const UVFontMap uvFontMap = makeUVFontMap({
.original = &fontMap,
.textureWidth = 256,
.textureHeight = 256
});
unsigned posX = 100;
const unsigned posY = 100;
for (const char chr : std::string_view("Dies ist ein Test-Text!"))
{
const UVFontMapEntry& entry = uvFontMap.entries[chr < 0 ? '_' : chr];
const UIVertex topLeft = {
.pos = {posX + entry.xOffset, posY + entry.yOffset},
.texcoord = {entry.uvX, entry.uvY}
};
const UIVertex bottomRight = {
.pos = {topLeft.pos.x + static_cast<float>(entry.width), topLeft.pos.y + static_cast<float>(entry.height)},
.texcoord = {entry.uvX + entry.uvWidth, entry.uvY + entry.uvHeight}
};
const UIVertex bottomLeft = {
.pos = {topLeft.pos.x, bottomRight.pos.y},
.texcoord = {topLeft.texcoord.x, bottomRight.texcoord.y}
};
const UIVertex topRight = {
.pos = {bottomRight.pos.x, topLeft.pos.y},
.texcoord = {bottomRight.texcoord.x, topLeft.texcoord.y}
};
mUIVertices.push_back(topLeft);
mUIVertices.push_back(bottomLeft);
mUIVertices.push_back(topRight);
mUIVertices.push_back(bottomRight);
mUIVertices.push_back(topRight);
mUIVertices.push_back(bottomLeft);
posX += entry.xAdvance;
}
// create UI vertex buffer
mUIVertexBuffer.create(device, {
.usage = {.vertex = true},
.size = static_cast<Uint32>(mUIVertices.size() * sizeof(UIVertex))
});
application.uploadVertexData(mUIVertexBuffer, std::span(mUIVertices));
// create texture and sampler
sdlpp::GPUTextureCreateArgs textureArgs = {
.format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB,
.usage = {.sampler = true}
};
mUITexture = application.loadTexture("bitmaps/ui.png", textureArgs);
mUISampler.create(device, {});
}
void UIRenderer::render(const UIRendererRenderArgs& args)
{
if (!mUIVertices.empty())
{
const UIVertexShaderParameters uiVertexShaderParameters = {
.windowSize = {
static_cast<float>(args.targetTextureWidth), static_cast<float>(args.targetTextureHeight)
}
};
std::array colorTargets = {sdlpp::GPUColorTargetInfo{
.texture = *args.targetTexture,
.clearColor = {.r = 0.f, .g = 0.f, .b = 0.f, .a = 1.f},
.loadOp = sdlpp::GPULoadOp::LOAD,
}};
colorTargets[0].loadOp = sdlpp::GPULoadOp::LOAD; // don't clear again
sdlpp::GPURenderPass renderPass = args.cmdBuffer->beginRenderPass({
.colorTargetInfos = colorTargets
});
static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f);
args.cmdBuffer->pushFragmentUniformData(0, std::span(&WHITE, 1));
args.cmdBuffer->pushVertexUniformData(0, std::span(&uiVertexShaderParameters, 1));
renderPass.bindFragmentSampler({.texture = mUITexture, .sampler = mUISampler});
renderPass.bindGraphicsPipeline(mUIPipeline);
renderPass.bindVertexBuffer({.buffer = mUIVertexBuffer});
renderPass.drawPrimitives({.numVertices = static_cast<Uint32>(mUIVertices.size())});
renderPass.end();
}
}
}

View File

@ -0,0 +1,44 @@
#pragma once
#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_6_UI_UI_UI_RENDERER_HPP_INCLUDED)
#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_6_UI_UI_UI_RENDERER_HPP_INCLUDED 1
#include <glm/vec2.hpp>
#include "../../application.hpp"
#include "../../sdlpp/gpu.hpp"
#include "../../util/font_map.hpp"
namespace sdl_gpu_test::inline app6
{
struct UIVertex
{
glm::vec2 pos;
glm::vec2 texcoord;
};
struct UIRendererRenderArgs
{
const sdlpp::GPUCommandBuffer* cmdBuffer;
const sdlpp::GPUTexture* targetTexture;
unsigned targetTextureWidth;
unsigned targetTextureHeight;
};
class UIRenderer
{
private:
Application* mApplication = nullptr;
sdlpp::GPUBuffer mUIVertexBuffer;
sdlpp::GPUGraphicsPipeline mUIPipeline;
std::vector<UIVertex> mUIVertices;
sdlpp::GPUTexture mUITexture;
sdlpp::GPUSampler mUISampler;
UVFontMap mFontMap;
public:
void init(Application& application);
void render(const UIRendererRenderArgs& args);
};
} // namespace sdl_gpu_test::inline app6
#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_6_UI_UI_UI_RENDERER_HPP_INCLUDED)

View File

@ -16,6 +16,7 @@ src_files = Split("""
4_textured_cube/app.cpp
5_input/app.cpp
6_ui/app.cpp
6_ui/ui/ui_renderer.cpp
""")
shader_files = env.Glob("#assets/shaders/glsl/*.frag") \

View File

@ -40,6 +40,15 @@ protected:
public:
virtual ~Application() noexcept = default;
[[nodiscard]]
const sdlpp::Window& getWindow() const noexcept { return mWindow; }
[[nodiscard]]
const sdlpp::GPUDevice& getDevice() const noexcept { return mDevice; }
[[nodiscard]]
mijin::StackedFileSystemAdapter& getFileSystem() noexcept { return mFileSystem; }
virtual void init(const AppInitArgs& args);
virtual void cleanup(const AppCleanupArgs& args);
virtual void update(const AppUpdateArgs& args);