From 4ad274d6371f3445b32e013e8be94ebfd2e80d5c Mon Sep 17 00:00:00 2001 From: Patrick Wuttke Date: Sun, 15 Sep 2024 10:42:54 +0200 Subject: [PATCH] Added texture and fixed a bunch of stuff. --- assets/bitmaps/sdl.png | Bin 0 -> 70978 bytes assets/shaders/glsl/color_from_texture.frag | 21 ++ private/sdl_gpu_test/1_textured_quad/app.cpp | 135 +++++++++ private/sdl_gpu_test/1_textured_quad/app.hpp | 24 ++ private/sdl_gpu_test/SModule | 12 +- private/sdl_gpu_test/application.cpp | 55 ++++ private/sdl_gpu_test/application.hpp | 5 + private/sdl_gpu_test/main.cpp | 7 +- private/sdl_gpu_test/sdlpp/common.hpp | 26 ++ private/sdl_gpu_test/sdlpp/gpu.hpp | 284 +++++++++++++++++-- private/sdl_gpu_test/sdlpp/window.hpp | 4 +- private/sdl_gpu_test/util/bitmap.cpp | 78 +++++ private/sdl_gpu_test/util/bitmap.hpp | 32 +++ 13 files changed, 640 insertions(+), 43 deletions(-) create mode 100644 assets/bitmaps/sdl.png create mode 100644 assets/shaders/glsl/color_from_texture.frag create mode 100644 private/sdl_gpu_test/1_textured_quad/app.cpp create mode 100644 private/sdl_gpu_test/1_textured_quad/app.hpp create mode 100644 private/sdl_gpu_test/util/bitmap.cpp create mode 100644 private/sdl_gpu_test/util/bitmap.hpp diff --git a/assets/bitmaps/sdl.png b/assets/bitmaps/sdl.png new file mode 100644 index 0000000000000000000000000000000000000000..03c4dba1328397a2bfd195ab95a18d72c3c89f1d GIT binary patch literal 70978 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 z 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 An*aa+ literal 0 HcmV?d00001 diff --git a/assets/shaders/glsl/color_from_texture.frag b/assets/shaders/glsl/color_from_texture.frag new file mode 100644 index 0000000..b548abf --- /dev/null +++ b/assets/shaders/glsl/color_from_texture.frag @@ -0,0 +1,21 @@ +#version 460 + +layout(set = 2, binding = 0) +uniform sampler2D u_texture; + +layout(set = 3, binding = 0) +uniform Parameters +{ + vec4 u_color; +}; + +layout(location = 0) +in vec2 i_texCoord; + +layout(location = 0) +out vec4 o_color; + +void main() +{ + o_color = texture(u_texture, i_texCoord) * u_color; +} diff --git a/private/sdl_gpu_test/1_textured_quad/app.cpp b/private/sdl_gpu_test/1_textured_quad/app.cpp new file mode 100644 index 0000000..1ca84b7 --- /dev/null +++ b/private/sdl_gpu_test/1_textured_quad/app.cpp @@ -0,0 +1,135 @@ + +#include "./app.hpp" + +#include +#include + +#include "../util/bitmap.hpp" + +namespace sdl_gpu_test +{ +namespace +{ +struct Vertex +{ + glm::vec2 pos; + glm::vec2 texCoord; +}; + +const std::array VERTICES = +{ + Vertex{.pos = { 0.7f, -0.7f}, .texCoord = {1.f, 1.f}}, + Vertex{.pos = { 0.7f, 0.7f}, .texCoord = {1.f, 0.f}}, + Vertex{.pos = {-0.7f, -0.7f}, .texCoord = {0.f, 1.f}}, + Vertex{.pos = {-0.7f, 0.7f}, .texCoord = {0.f, 0.f}} +}; +} + +void TexturedQuadApp::init(const AppInitArgs& args) +{ + Application::init(args); + + // create shaders + sdlpp::GPUShader vertexShader = loadShader("shaders/glsl/textured_triangles_from_buffer.vert.spv", { + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::VERTEX + }); + sdlpp::GPUShader fragmentShader = loadShader("shaders/glsl/color_from_texture.frag.spv", { + .format = sdlpp::GPUShaderFormat::SPIRV, + .stage = sdlpp::GPUShaderStage::FRAGMENT, + .numSamplers = 1, + .numUniformBuffers = 1 + }); + + // create graphics pipeline + 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(Vertex) + } + }; + std::array vertexAttributes = { + sdlpp::GPUVertexAttribute{ + .location = 0, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT2, + .offset = offsetof(Vertex, pos) + }, + sdlpp::GPUVertexAttribute{ + .location = 1, + .bindingIndex = 0, + .format = sdlpp::GPUVertexElementFormat::FLOAT2, + .offset = offsetof(Vertex, texCoord) + } + }; + mPipeline.create(mDevice, { + .vertexShader = vertexShader, + .fragmentShader = fragmentShader, + .vertexInputState = { + .vertexBindings = vertexBindings, + .vertexAttributes = vertexAttributes + }, + .primitiveType = sdlpp::GPUPrimitiveType::TRIANGLESTRIP, + .rasterizerState = { + .cullMode = sdlpp::GPUCullMode::BACK + }, + .targetInfo = { + .colorTargetDescriptions = colorTargetsDescs + } + }); + + // create vertex buffer + mVertexBuffer.create(mDevice, { + .usage = {.vertex = true}, + .size = sizeof(VERTICES) + }); + uploadVertexData(mVertexBuffer, std::span(VERTICES.begin(), VERTICES.end())); + + // create texture and sampler + sdlpp::GPUTextureCreateArgs textureArgs = { + .format = sdlpp::GPUTextureFormat::R8G8B8A8_UNORM_SRGB, + .usage = {.sampler = true} + }; + mTexture = loadTexture("bitmaps/sdl.png", textureArgs); + mSampler.create(mDevice, {}); +} + +void TexturedQuadApp::update(const AppUpdateArgs& args) +{ + Application::update(args); + + sdlpp::GPUCommandBuffer cmdBuffer = mDevice.acquireCommandBuffer(); + Uint32 swapchainWidth = 0, swapchainHeight = 0; + sdlpp::GPUTexture swapchainTexture = cmdBuffer.acquireSwapchainTexture(mWindow, swapchainWidth, swapchainHeight); + std::array colorTargets = {sdlpp::GPUColorTargetInfo{ + .texture = swapchainTexture, + .clearColor = {.r = 1.f, .g = 0.f, .b = 0.f, .a = 1.f}, + .loadOp = sdlpp::GPULoadOp::CLEAR, + }}; + sdlpp::GPURenderPass renderPass = cmdBuffer.beginRenderPass({ + .colorTargetInfos = colorTargets + }); + static const glm::vec4 WHITE(1.f, 1.f, 1.f, 1.f); + cmdBuffer.pushFragmentUniformData(0, std::span(&WHITE, 1)); + renderPass.bindFragmentSampler({.texture = mTexture, .sampler = mSampler}); + renderPass.bindGraphicsPipeline(mPipeline); + renderPass.bindVertexBuffer({.buffer = mVertexBuffer}); + renderPass.drawPrimitives({.numVertices = VERTICES.size()}); + renderPass.end(); + cmdBuffer.submit(); +} +} diff --git a/private/sdl_gpu_test/1_textured_quad/app.hpp b/private/sdl_gpu_test/1_textured_quad/app.hpp new file mode 100644 index 0000000..3debb8c --- /dev/null +++ b/private/sdl_gpu_test/1_textured_quad/app.hpp @@ -0,0 +1,24 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_1_TEXTURED_QUAD_APP_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_1_TEXTURED_QUAD_APP_HPP_INCLUDED 1 + +#include "../application.hpp" + +namespace sdl_gpu_test +{ +class TexturedQuadApp : public Application +{ +private: + sdlpp::GPUBuffer mVertexBuffer; + sdlpp::GPUGraphicsPipeline mPipeline; + sdlpp::GPUTexture mTexture; + sdlpp::GPUSampler mSampler; +public: + void init(const AppInitArgs& args) override; + void update(const AppUpdateArgs& args) override; +}; +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_1_TEXTURED_QUAD_APP_HPP_INCLUDED) diff --git a/private/sdl_gpu_test/SModule b/private/sdl_gpu_test/SModule index c4719ce..f6826ad 100644 --- a/private/sdl_gpu_test/SModule +++ b/private/sdl_gpu_test/SModule @@ -5,18 +5,14 @@ src_files = Split(""" main.cpp application.cpp + util/bitmap.cpp 0_triangle_with_texcoords/app.cpp + 1_textured_quad/app.cpp """) -shader_files = Split(""" - #assets/shaders/glsl/color_from_uniform.frag - #assets/shaders/glsl/color_from_texcoord.frag - #assets/shaders/glsl/green.frag - #assets/shaders/glsl/triangle.vert - #assets/shaders/glsl/triangles_from_buffer.vert - #assets/shaders/glsl/textured_triangles_from_buffer.vert -""") +shader_files = env.Glob("#assets/shaders/glsl/*.frag") \ + + env.Glob("#assets/shaders/glsl/*.vert") env.Append(CPPDEFINES = ['GLM_FORCE_DEPTH_ZERO_TO_ONE', 'GLM_FORCE_RADIANS']) prog_app = env.UnityProgram( diff --git a/private/sdl_gpu_test/application.cpp b/private/sdl_gpu_test/application.cpp index 2928aeb..f6bb0e5 100644 --- a/private/sdl_gpu_test/application.cpp +++ b/private/sdl_gpu_test/application.cpp @@ -3,6 +3,10 @@ #include #include +#include +#include + +#include "./util/bitmap.hpp" namespace sdl_gpu_test { @@ -19,6 +23,16 @@ void Application::init(const AppInitArgs& args) }); mDevice.claimWindow(mWindow); + // setup swapchain + if (mDevice.windowSupportsSwapchainComposition(mWindow, sdlpp::GPUSwapchainComposition::SDR_LINEAR)) + { + mDevice.setSwapchainParameters(mWindow, sdlpp::GPUSwapchainComposition::SDR_LINEAR, sdlpp::GPUPresentMode::VSYNC); + } + else + { + spdlog::warn("Swapchain does not support SRGB, image will not look correct."); + } + fs::path executablePath = fs::absolute(fs::path(args.programArgs[0])).parent_path(); mFileSystem.emplaceAdapter>(executablePath.parent_path() / "assets"); } @@ -82,4 +96,45 @@ sdlpp::GPUShader Application::loadShader(const fs::path& path, sdlpp::GPUShaderC shader.create(mDevice, args); return shader; } + +sdlpp::GPUTexture Application::loadTexture(const fs::path& path, sdlpp::GPUTextureCreateArgs& inout_args) +{ + const Bitmap bitmap = loadBitmap(mFileSystem.getPath(path)); + inout_args.width = bitmap.width, + inout_args.height = bitmap.height; + + sdlpp::GPUTexture texture; + texture.create(mDevice, inout_args); + uploadTextureData(texture, bitmap); + return texture; +} + +void Application::uploadTextureData(const sdlpp::GPUTexture& texture, const Bitmap& bitmap) const +{ + sdlpp::GPUTransferBuffer transferBuffer; + transferBuffer.create(mDevice, { + .usage = sdlpp::GPUTransferBufferUsage::UPLOAD, + .size = static_cast(bitmap.pixels.size() * sizeof(Pixel)) + }); + void* ptr = transferBuffer.map(); + std::memcpy(ptr, bitmap.pixels.data(), bitmap.pixels.size() * sizeof(Pixel)); + transferBuffer.unmap(); + + sdlpp::GPUCommandBuffer cmdBuffer = mDevice.acquireCommandBuffer(); + sdlpp::GPUCopyPass copyPass = cmdBuffer.beginCopyPass(); + copyPass.uploadToGPUTexture( + /* source = */ { + .transferBuffer = transferBuffer, + .pixelsPerRow = bitmap.width, + .rowsPerLayer = bitmap.height + }, + /* destination = */ { + .texture = texture, + .width = bitmap.width, + .height = bitmap.height + } + ); + copyPass.end(); + cmdBuffer.submit(); +} } diff --git a/private/sdl_gpu_test/application.hpp b/private/sdl_gpu_test/application.hpp index d0d4f57..348d4f8 100644 --- a/private/sdl_gpu_test/application.hpp +++ b/private/sdl_gpu_test/application.hpp @@ -55,6 +55,11 @@ public: [[nodiscard]] sdlpp::GPUShader loadShader(const fs::path& path, sdlpp::GPUShaderCreateArgs args); + [[nodiscard]] + sdlpp::GPUTexture loadTexture(const fs::path& path, sdlpp::GPUTextureCreateArgs& inout_args); + + void uploadTextureData(const sdlpp::GPUTexture& texture, const struct Bitmap& bitmap) const; + template void uploadVertexData(const sdlpp::GPUBuffer& vertexBuffer, std::span vertices) const; }; diff --git a/private/sdl_gpu_test/main.cpp b/private/sdl_gpu_test/main.cpp index 34114da..5e1ab9c 100644 --- a/private/sdl_gpu_test/main.cpp +++ b/private/sdl_gpu_test/main.cpp @@ -1,5 +1,6 @@ #include "./0_triangle_with_texcoords/app.hpp" +#include "./1_textured_quad/app.hpp" #include #include @@ -14,13 +15,15 @@ int main(int argc, char* argv[]) if (SDL_Init(0) != SDL_TRUE) { - throw std::runtime_error("Error initializing SDL."); + spdlog::error("Error initializing SDL."); + return 1; } try { // make sure app is destructed before shutting down SDL - std::unique_ptr app = std::make_unique(); + // std::unique_ptr app = std::make_unique(); + std::unique_ptr app = std::make_unique(); app->run(std::span(const_cast(argv), argc)); } catch (std::exception& exception) diff --git a/private/sdl_gpu_test/sdlpp/common.hpp b/private/sdl_gpu_test/sdlpp/common.hpp index bac267c..57d71c1 100644 --- a/private/sdl_gpu_test/sdlpp/common.hpp +++ b/private/sdl_gpu_test/sdlpp/common.hpp @@ -49,6 +49,32 @@ public: operator THandle*() const noexcept { return mHandle; } }; +template +class BaseWithDevice : public Base +{ +protected: + SDL_GPUDevice* mDevice = nullptr; +protected: + BaseWithDevice() noexcept = default; + BaseWithDevice(THandle* handle, SDL_GPUDevice* device) noexcept : Base(handle), mDevice(device) {} + BaseWithDevice(BaseWithDevice&& other) noexcept : Base(std::exchange(other.mHandle, nullptr)), + mDevice(std::exchange(other.mDevice, nullptr)){} + + BaseWithDevice& operator=(BaseWithDevice&& other) noexcept + { + if (this != &other) + { + mDevice = std::exchange(other.mDevice, nullptr); + Base::operator=(std::move(other)); + } + return *this; + } +public: + BaseWithDevice(const BaseWithDevice&) = delete; + BaseWithDevice& operator=(const BaseWithDevice&) = delete; + auto operator<=>(const BaseWithDevice& other) const noexcept = default; +}; + class SDLError : public std::runtime_error { public: diff --git a/private/sdl_gpu_test/sdlpp/gpu.hpp b/private/sdl_gpu_test/sdlpp/gpu.hpp index 6ec12b7..802e86b 100644 --- a/private/sdl_gpu_test/sdlpp/gpu.hpp +++ b/private/sdl_gpu_test/sdlpp/gpu.hpp @@ -8,6 +8,8 @@ namespace sdlpp { +static_assert(sizeof(SDL_bool) == sizeof(bool)); // we assume this in the whole file... + // // enums // @@ -137,6 +139,14 @@ enum class GPUBlendOp MAX = SDL_GPU_BLENDOP_MAX }; +enum class GPUTextureType +{ + TWOD = SDL_GPU_TEXTURETYPE_2D, + TWOD_ARRAY = SDL_GPU_TEXTURETYPE_2D_ARRAY, + THREED = SDL_GPU_TEXTURETYPE_3D, + CUBE = SDL_GPU_TEXTURETYPE_CUBE +}; + enum class GPUTextureFormat { INVALID = SDL_GPU_TEXTUREFORMAT_INVALID, @@ -198,6 +208,25 @@ enum class GPUTextureFormat D32_FLOAT_S8_UINT = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT }; +enum class GPUFilter +{ + NEAREST = SDL_GPU_FILTER_NEAREST, + LINEAR = SDL_GPU_FILTER_LINEAR +}; + +enum class GPUSamplerMipmapMode +{ + NEAREST = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST, + LINEAR = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR +}; + +enum class GPUSamplerAddressMode +{ + REPEAT = SDL_GPU_SAMPLERADDRESSMODE_REPEAT, + MIRRORED_REPEAT = SDL_GPU_SAMPLERADDRESSMODE_MIRRORED_REPEAT, + CLAMP_TO_EDGE = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE +}; + enum class GPUShaderFormat { PRIVATE = SDL_GPU_SHADERFORMAT_PRIVATE, @@ -233,6 +262,21 @@ enum class GPUTransferBufferUsage DOWNLOAD = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD }; +enum class GPUSwapchainComposition +{ + SDR = SDL_GPU_SWAPCHAINCOMPOSITION_SDR, + SDR_LINEAR = SDL_GPU_SWAPCHAINCOMPOSITION_SDR_LINEAR, + HDR_EXTENDED_LINEAR = SDL_GPU_SWAPCHAINCOMPOSITION_HDR_EXTENDED_LINEAR, + HDR10_ST2048 = SDL_GPU_SWAPCHAINCOMPOSITION_HDR10_ST2048 +}; + +enum class GPUPresentMode +{ + VSYNC = SDL_GPU_PRESENTMODE_VSYNC, + IMMEDIATE = SDL_GPU_PRESENTMODE_IMMEDIATE, + MAILBOX = SDL_GPU_PRESENTMODE_MAILBOX +}; + // // bitflags // @@ -274,6 +318,21 @@ struct GPUBufferUsageFlags : mijin::BitFlags } }; +struct GPUTextureUsageFlags : mijin::BitFlags +{ + bool sampler : 1 = false; + bool colorTarget : 1 = false; + bool depthStencilTarget : 1 = false; + bool graphicsStorageRead : 1 = false; + bool computeStorageRead : 1 = false; + bool computeStorageWrite : 1 = false; + + explicit operator SDL_GPUTextureUsageFlags() const noexcept + { + return std::bit_cast(*this); + } +}; + // // structs // @@ -432,8 +491,33 @@ struct GPUBufferRegion }; static_assert(sizeof(GPUBufferRegion) == sizeof(SDL_GPUBufferRegion)); +struct GPUTextureTransferInfo +{ + SDL_GPUTransferBuffer* transferBuffer; + Uint32 offset = 0; + Uint32 pixelsPerRow; + Uint32 rowsPerLayer; +}; +static_assert(sizeof(GPUTextureTransferInfo) == sizeof(SDL_GPUTextureTransferInfo)); + +struct GPUTextureRegion +{ + SDL_GPUTexture* texture; + Uint32 mipLevel = 0; + Uint32 layer = 0; + Uint32 x = 0; + Uint32 y = 0; + Uint32 z = 0; + Uint32 width; + Uint32 height = 1; + Uint32 depth = 1; +}; +static_assert(sizeof(GPUTextureRegion) == sizeof(SDL_GPUTextureRegion)); + using GPUBufferBinding = SDL_GPUBufferBinding; +using GPUTextureSamplerBinding = SDL_GPUTextureSamplerBinding; + // // classes // @@ -492,6 +576,21 @@ public: bindVertexBuffers({&binding, 1}, offset); } + void bindFragmentSamplers(std::span textureSamplerBindings, Uint32 firstSlot = 0) const noexcept + { + SDL_BindGPUFragmentSamplers( + /* render_pass = */ mHandle, + /* first_slot = */ firstSlot, + /* texture_sampler_bindings = */ textureSamplerBindings.data(), + /* num_bindings = */ static_cast(textureSamplerBindings.size()) + ); + } + + void bindFragmentSampler(const GPUTextureSamplerBinding& binding, Uint32 offset = 0) const noexcept + { + bindFragmentSamplers({&binding, 1}, offset); + } + void drawPrimitives(const GPUDrawPrimitivesArgs& args) const noexcept { SDL_DrawGPUPrimitives(mHandle, args.numVertices, args.numInstances, args.firstVertex, args.firstInstance); @@ -529,33 +628,74 @@ public: void uploadToGPUBuffer(const GPUTransferBufferLocation& source, const GPUBufferRegion& destination, bool cycle = false) { SDL_UploadToGPUBuffer( - /* copy_pass = */ mHandle, - /* source = */ std::bit_cast(&source), - /* destination = */ std::bit_cast(&destination), - /* cycle = */ cycle - ); + /* copy_pass = */ mHandle, + /* source = */ std::bit_cast(&source), + /* destination = */ std::bit_cast(&destination), + /* cycle = */ cycle + ); + } + + void uploadToGPUTexture(const GPUTextureTransferInfo& source, const GPUTextureRegion& destination, bool cycle = false) + { + SDL_UploadToGPUTexture( + /* copy_pass = */ mHandle, + /* source = */ std::bit_cast(&source), + /* destination = */ std::bit_cast(&destination), + /* cycle = */ cycle + ); } friend class GPUCommandBuffer; }; -class GPUTexture : public Base +struct GPUTextureCreateArgs +{ + GPUTextureType type = GPUTextureType::TWOD; + GPUTextureFormat format = GPUTextureFormat::R8G8B8A8_UNORM; + GPUTextureUsageFlags usage; + Uint32 width = 1; + Uint32 height = 1; + Uint32 layerCountOrDepth = 1; + Uint32 numLevels = 1; + GPUSampleCount sampleCount = GPUSampleCount::ONE; +}; + +class GPUTexture : public BaseWithDevice { -private: - SDL_GPUDevice* mDevice = nullptr; public: GPUTexture() noexcept = default; GPUTexture(const GPUTexture&) = delete; - GPUTexture(GPUTexture&& other) noexcept : Base(std::move(other)) {} + GPUTexture(GPUTexture&& other) noexcept : BaseWithDevice(std::move(other)) {} GPUTexture& operator=(const GPUTexture&) = delete; GPUTexture& operator=(GPUTexture&& other) noexcept { - Base::operator=(std::move(other)); + BaseWithDevice::operator=(std::move(other)); return *this; } auto operator<=>(const GPUTexture& other) const noexcept = default; + void create(SDL_GPUDevice* device, const GPUTextureCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUTexture has already been created."); + const SDL_GPUTextureCreateInfo createInfo = { + .type = static_cast(args.type), + .format = static_cast(args.format), + .usage = static_cast(args.usage), + .width = args.width, + .height = args.height, + .layer_count_or_depth = args.layerCountOrDepth, + .num_levels = args.numLevels, + .sample_count = static_cast(args.sampleCount) + }; + mHandle = SDL_CreateGPUTexture(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + void destroy() noexcept { if (mHandle != nullptr) @@ -573,6 +713,75 @@ public: friend class GPUCommandBuffer; }; +struct GPUSamplerCreateArgs +{ + GPUFilter minFilter = GPUFilter::NEAREST; + GPUFilter magFilter = GPUFilter::LINEAR; + GPUSamplerMipmapMode mipmapMode = GPUSamplerMipmapMode::LINEAR; + GPUSamplerAddressMode addressModeU = GPUSamplerAddressMode::REPEAT; + GPUSamplerAddressMode addressModeV = GPUSamplerAddressMode::REPEAT; + GPUSamplerAddressMode addressModeW = GPUSamplerAddressMode::REPEAT; + float mipLodBias = 0.f; + float maxAnisotropy = 1.f; + bool enableAnisotropy = false; + bool enableCompare = false; + GPUCompareOp compareOp; + float minLod = 0.f; + float maxLod = 1.f; +}; + +class GPUSampler : public BaseWithDevice +{ +public: + GPUSampler() noexcept = default; + GPUSampler(const GPUSampler&) = delete; + GPUSampler(GPUSampler&& other) noexcept : BaseWithDevice(std::move(other)) {} + + GPUSampler& operator=(const GPUSampler&) = delete; + GPUSampler& operator=(GPUSampler&& other) noexcept + { + BaseWithDevice::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUSampler& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUSamplerCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUSampler has already been created."); + const SDL_GPUSamplerCreateInfo createInfo = { + .min_filter = static_cast(args.minFilter), + .mag_filter = static_cast(args.magFilter), + .mipmap_mode = static_cast(args.mipmapMode), + .address_mode_u = static_cast(args.addressModeU), + .address_mode_v = static_cast(args.addressModeV), + .address_mode_w = static_cast(args.addressModeW), + .mip_lod_bias = args.mipLodBias, + .max_anisotropy = args.maxAnisotropy, + .enable_anisotropy = args.enableAnisotropy, + .enable_compare = args.enableCompare, + .compare_op = static_cast(args.compareOp), + .min_lod = args.minLod, + .max_lod = args.maxLod + }; + mHandle = SDL_CreateGPUSampler(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUSampler(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } +}; + struct GPUBeginRenderPassArgs { std::span colorTargetInfos; @@ -697,6 +906,28 @@ public: } } + [[nodiscard]] + bool windowSupportsSwapchainComposition(SDL_Window* window, GPUSwapchainComposition swapchainComposition) const noexcept + { + return SDL_WindowSupportsGPUSwapchainComposition(mHandle, window, static_cast(swapchainComposition)); + } + + [[nodiscard]] + bool windowSupportsPresentMode(SDL_Window* window, GPUPresentMode presentMode) const noexcept + { + return SDL_WindowSupportsGPUPresentMode(mHandle, window, static_cast(presentMode)); + } + + void setSwapchainParameters(SDL_Window* window, GPUSwapchainComposition swapchainComposition, + GPUPresentMode presentMode) const + { + if (!SDL_SetGPUSwapchainParameters(mHandle, window, static_cast(swapchainComposition), + static_cast(presentMode))) + { + throw SDLError(); + } + } + [[nodiscard]] GPUTextureFormat getSwapchainTextureFormat(SDL_Window* window) const { @@ -724,20 +955,17 @@ struct GPUGraphicsPipelineCreateArgs GPUGraphicsPipelineTargetInfo targetInfo; }; -class GPUGraphicsPipeline : public Base +class GPUGraphicsPipeline : public BaseWithDevice { -private: - SDL_GPUDevice* mDevice = nullptr; public: GPUGraphicsPipeline() noexcept = default; GPUGraphicsPipeline(const GPUGraphicsPipeline&) = delete; - GPUGraphicsPipeline(GPUGraphicsPipeline&& other) noexcept : Base(std::move(other)) {} + GPUGraphicsPipeline(GPUGraphicsPipeline&& other) noexcept : BaseWithDevice(std::move(other)) {} GPUGraphicsPipeline& operator=(const GPUGraphicsPipeline&) = delete; GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept { - Base::operator=(std::move(other)); - mDevice = other.mDevice; + BaseWithDevice::operator=(std::move(other)); return *this; } auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default; @@ -813,19 +1041,17 @@ struct GPUShaderCreateArgs Uint32 numUniformBuffers = 0; }; -class GPUShader : public Base +class GPUShader : public BaseWithDevice { -private: - SDL_GPUDevice* mDevice = nullptr; public: GPUShader() noexcept = default; GPUShader(const GPUShader&) = delete; - GPUShader(GPUShader&& other) noexcept : Base(std::move(other)) {} + GPUShader(GPUShader&& other) noexcept : BaseWithDevice(std::move(other)) {} GPUShader& operator=(const GPUShader&) = delete; GPUShader& operator=(GPUShader&& other) noexcept { - Base::operator=(std::move(other)); + BaseWithDevice::operator=(std::move(other)); return *this; } auto operator<=>(const GPUShader& other) const noexcept = default; @@ -870,19 +1096,17 @@ struct GPUBufferCreateArgs Uint32 size; }; -class GPUBuffer : public Base +class GPUBuffer : public BaseWithDevice { -private: - SDL_GPUDevice* mDevice = nullptr; public: GPUBuffer() noexcept = default; GPUBuffer(const GPUBuffer&) = delete; - GPUBuffer(GPUBuffer&& other) noexcept : Base(std::move(other)) {} + GPUBuffer(GPUBuffer&& other) noexcept : BaseWithDevice(std::move(other)) {} GPUBuffer& operator=(const GPUBuffer&) = delete; GPUBuffer& operator=(GPUBuffer&& other) noexcept { - Base::operator=(std::move(other)); + BaseWithDevice::operator=(std::move(other)); return *this; } auto operator<=>(const GPUBuffer& other) const noexcept = default; @@ -920,19 +1144,17 @@ struct GPUTransferBufferCreateArgs Uint32 size; }; -class GPUTransferBuffer : public Base +class GPUTransferBuffer : public BaseWithDevice { -private: - SDL_GPUDevice* mDevice = nullptr; public: GPUTransferBuffer() noexcept = default; GPUTransferBuffer(const GPUTransferBuffer&) = delete; - GPUTransferBuffer(GPUTransferBuffer&& other) noexcept : Base(std::move(other)) {} + GPUTransferBuffer(GPUTransferBuffer&& other) noexcept : BaseWithDevice(std::move(other)) {} GPUTransferBuffer& operator=(const GPUTransferBuffer&) = delete; GPUTransferBuffer& operator=(GPUTransferBuffer&& other) noexcept { - Base::operator=(std::move(other)); + BaseWithDevice::operator=(std::move(other)); return *this; } auto operator<=>(const GPUTransferBuffer& other) const noexcept = default; diff --git a/private/sdl_gpu_test/sdlpp/window.hpp b/private/sdl_gpu_test/sdlpp/window.hpp index 00d3b81..48f3ba8 100644 --- a/private/sdl_gpu_test/sdlpp/window.hpp +++ b/private/sdl_gpu_test/sdlpp/window.hpp @@ -13,7 +13,7 @@ struct WindowFlags : mijin::BitFlags bool fullscreen : 1; // 0x0000000000000001 /**< window is in fullscreen mode */ bool opengl : 1; // 0x0000000000000002 /**< window usable with OpenGL context */ bool occluded : 1; // 0x0000000000000004 /**< window is occluded */ - bool hidden : 1; // 0x0000000000000008) /**< window is neither mapped onto the desktop nor shown in the taskbar/dock/window list; SDL_ShowWindow( is required for it to become visible */ + bool hidden : 1; // 0x0000000000000008 /**< window is neither mapped onto the desktop nor shown in the taskbar/dock/window list; SDL_ShowWindow( is required for it to become visible */ bool borderless : 1; // 0x0000000000000010 /**< no window decoration */ bool resizable : 1; // 0x0000000000000020 /**< window can be resized */ bool minimized : 1; // 0x0000000000000040 /**< window is minimized */ @@ -24,7 +24,7 @@ struct WindowFlags : mijin::BitFlags bool external : 1; // 0x0000000000000800 /**< window not created by SDL */ bool modal : 1; // 0x0000000000001000 /**< window is modal */ bool high_pixel_density : 1; // 0x0000000000002000 /**< window uses high pixel density back buffer if possible */ - bool mouse_capture : 1; // 0x0000000000004000) /**< window has mouse captured (unrelated to MOUSE_GRABBED */ + bool mouse_capture : 1; // 0x0000000000004000 /**< window has mouse captured (unrelated to MOUSE_GRABBED */ bool mouse_relative_mode : 1; // 0x0000000000008000 /**< window has relative mode enabled */ bool always_on_top : 1; // 0x0000000000010000 /**< window should always be above others */ bool utility : 1; // 0x0000000000020000 /**< window should be treated as a utility window, not showing in the task bar and window list */ diff --git a/private/sdl_gpu_test/util/bitmap.cpp b/private/sdl_gpu_test/util/bitmap.cpp new file mode 100644 index 0000000..e5ce0ef --- /dev/null +++ b/private/sdl_gpu_test/util/bitmap.cpp @@ -0,0 +1,78 @@ + +#include "./bitmap.hpp" + +#include +#include + +#define STBI_ASSERT(x) MIJIN_ASSERT(x, #x) +#define STB_IMAGE_IMPLEMENTATION +#include +#undef STB_IMAGE_IMPLEMENTATION +#undef STBI_ASSERT + +namespace sdl_gpu_test +{ +namespace +{ + +int stbiReadCallback(void* user, char* data, int size) +{ + mijin::Stream& stream = *static_cast(user); + std::size_t bytesRead = 0; + const mijin::StreamError error = stream.readRaw(data, size, {.partial = true}, &bytesRead); + if (error != mijin::StreamError::SUCCESS) + { + // TODO: return what? + return 0; + } + return static_cast(bytesRead); +} + +void stbiSkipCallback(void* user, int bytes) +{ + mijin::Stream& stream = *static_cast(user); + (void) stream.seek(bytes, mijin::SeekMode::RELATIVE); +} + +int stbiEofCallback(void* user) +{ + mijin::Stream& stream = *static_cast(user); + return stream.isAtEnd(); +} +} + +Bitmap loadBitmap(const mijin::PathReference& path) +{ + std::unique_ptr stream; + mijin::throwOnError(path.open(mijin::FileOpenMode::READ, stream)); + + const stbi_io_callbacks callbacks = { + .read = &stbiReadCallback, + .skip = &stbiSkipCallback, + .eof = &stbiEofCallback + }; + + int width, height, components; + stbi_uc* data = stbi_load_from_callbacks( + /* clbk = */ &callbacks, + /* user = */ stream.get(), + /* x = */ &width, + /* y = */ &height, + /* channels_in_file = */ &components, + /* desired_channels = */ 4 + ); + if (data == nullptr) + { + throw std::runtime_error(std::format("Could not load bitmap: {}.", stbi_failure_reason())); + } + + std::vector pixels; + pixels.resize(static_cast(width) * height); + std::memcpy(pixels.data(), data, pixels.size() * sizeof(Pixel)); + return { + .width = static_cast(width), + .height = static_cast(height), + .pixels = std::move(pixels) + }; +} +} diff --git a/private/sdl_gpu_test/util/bitmap.hpp b/private/sdl_gpu_test/util/bitmap.hpp new file mode 100644 index 0000000..5e6080d --- /dev/null +++ b/private/sdl_gpu_test/util/bitmap.hpp @@ -0,0 +1,32 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_BITMAP_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_BITMAP_HPP_INCLUDED 1 + +#include +#include + +#include + +namespace sdl_gpu_test +{ +struct Pixel +{ + std::uint8_t r; + std::uint8_t g; + std::uint8_t b; + std::uint8_t a; +}; +struct Bitmap +{ + unsigned width; + unsigned height; + std::vector pixels; +}; + +[[nodiscard]] +Bitmap loadBitmap(const mijin::PathReference& path); +} // namespace sdl_gpu_test + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_UTIL_BITMAP_HPP_INCLUDED)