diff --git a/assets/bitmaps/sdl.png b/assets/bitmaps/sdl.png new file mode 100644 index 0000000..03c4dba Binary files /dev/null and b/assets/bitmaps/sdl.png differ 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)