Added texture and fixed a bunch of stuff.

This commit is contained in:
Patrick 2024-09-15 10:42:54 +02:00
parent 9095c7e99a
commit 4ad274d637
13 changed files with 640 additions and 43 deletions

BIN
assets/bitmaps/sdl.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

View File

@ -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;
}

View File

@ -0,0 +1,135 @@
#include "./app.hpp"
#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
#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();
}
}

View File

@ -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)

View File

@ -5,18 +5,14 @@ src_files = Split("""
main.cpp main.cpp
application.cpp application.cpp
util/bitmap.cpp
0_triangle_with_texcoords/app.cpp 0_triangle_with_texcoords/app.cpp
1_textured_quad/app.cpp
""") """)
shader_files = Split(""" shader_files = env.Glob("#assets/shaders/glsl/*.frag") \
#assets/shaders/glsl/color_from_uniform.frag + env.Glob("#assets/shaders/glsl/*.vert")
#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
""")
env.Append(CPPDEFINES = ['GLM_FORCE_DEPTH_ZERO_TO_ONE', 'GLM_FORCE_RADIANS']) env.Append(CPPDEFINES = ['GLM_FORCE_DEPTH_ZERO_TO_ONE', 'GLM_FORCE_RADIANS'])
prog_app = env.UnityProgram( prog_app = env.UnityProgram(

View File

@ -3,6 +3,10 @@
#include <mijin/util/variant.hpp> #include <mijin/util/variant.hpp>
#include <mijin/virtual_filesystem/relative.hpp> #include <mijin/virtual_filesystem/relative.hpp>
#include <spdlog/spdlog.h>
#include <mijin/util/winundef.hpp>
#include "./util/bitmap.hpp"
namespace sdl_gpu_test namespace sdl_gpu_test
{ {
@ -19,6 +23,16 @@ void Application::init(const AppInitArgs& args)
}); });
mDevice.claimWindow(mWindow); 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(); fs::path executablePath = fs::absolute(fs::path(args.programArgs[0])).parent_path();
mFileSystem.emplaceAdapter<mijin::RelativeFileSystemAdapter<mijin::OSFileSystemAdapter>>(executablePath.parent_path() / "assets"); mFileSystem.emplaceAdapter<mijin::RelativeFileSystemAdapter<mijin::OSFileSystemAdapter>>(executablePath.parent_path() / "assets");
} }
@ -82,4 +96,45 @@ sdlpp::GPUShader Application::loadShader(const fs::path& path, sdlpp::GPUShaderC
shader.create(mDevice, args); shader.create(mDevice, args);
return shader; 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<Uint32>(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();
}
} }

View File

@ -55,6 +55,11 @@ public:
[[nodiscard]] [[nodiscard]]
sdlpp::GPUShader loadShader(const fs::path& path, sdlpp::GPUShaderCreateArgs args); 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<typename TVertex> template<typename TVertex>
void uploadVertexData(const sdlpp::GPUBuffer& vertexBuffer, std::span<TVertex> vertices) const; void uploadVertexData(const sdlpp::GPUBuffer& vertexBuffer, std::span<TVertex> vertices) const;
}; };

View File

@ -1,5 +1,6 @@
#include "./0_triangle_with_texcoords/app.hpp" #include "./0_triangle_with_texcoords/app.hpp"
#include "./1_textured_quad/app.hpp"
#include <mijin/debug/stacktrace.hpp> #include <mijin/debug/stacktrace.hpp>
#include <spdlog/spdlog.h> #include <spdlog/spdlog.h>
@ -14,13 +15,15 @@ int main(int argc, char* argv[])
if (SDL_Init(0) != SDL_TRUE) if (SDL_Init(0) != SDL_TRUE)
{ {
throw std::runtime_error("Error initializing SDL."); spdlog::error("Error initializing SDL.");
return 1;
} }
try try
{ {
// make sure app is destructed before shutting down SDL // make sure app is destructed before shutting down SDL
std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TriangleWithTexcoordsApp>(); // std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TriangleWithTexcoordsApp>();
std::unique_ptr<sdl_gpu_test::Application> app = std::make_unique<sdl_gpu_test::TexturedQuadApp>();
app->run(std::span(const_cast<const char**>(argv), argc)); app->run(std::span(const_cast<const char**>(argv), argc));
} }
catch (std::exception& exception) catch (std::exception& exception)

View File

@ -49,6 +49,32 @@ public:
operator THandle*() const noexcept { return mHandle; } operator THandle*() const noexcept { return mHandle; }
}; };
template<typename THandle, typename TConcrete>
class BaseWithDevice : public Base<THandle, TConcrete>
{
protected:
SDL_GPUDevice* mDevice = nullptr;
protected:
BaseWithDevice() noexcept = default;
BaseWithDevice(THandle* handle, SDL_GPUDevice* device) noexcept : Base<THandle, TConcrete>(handle), mDevice(device) {}
BaseWithDevice(BaseWithDevice&& other) noexcept : Base<THandle, TConcrete>(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<THandle, TConcrete>::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 class SDLError : public std::runtime_error
{ {
public: public:

View File

@ -8,6 +8,8 @@
namespace sdlpp namespace sdlpp
{ {
static_assert(sizeof(SDL_bool) == sizeof(bool)); // we assume this in the whole file...
// //
// enums // enums
// //
@ -137,6 +139,14 @@ enum class GPUBlendOp
MAX = SDL_GPU_BLENDOP_MAX 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 enum class GPUTextureFormat
{ {
INVALID = SDL_GPU_TEXTUREFORMAT_INVALID, INVALID = SDL_GPU_TEXTUREFORMAT_INVALID,
@ -198,6 +208,25 @@ enum class GPUTextureFormat
D32_FLOAT_S8_UINT = SDL_GPU_TEXTUREFORMAT_D32_FLOAT_S8_UINT 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 enum class GPUShaderFormat
{ {
PRIVATE = SDL_GPU_SHADERFORMAT_PRIVATE, PRIVATE = SDL_GPU_SHADERFORMAT_PRIVATE,
@ -233,6 +262,21 @@ enum class GPUTransferBufferUsage
DOWNLOAD = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD 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 // bitflags
// //
@ -274,6 +318,21 @@ struct GPUBufferUsageFlags : mijin::BitFlags<GPUBufferUsageFlags>
} }
}; };
struct GPUTextureUsageFlags : mijin::BitFlags<GPUTextureUsageFlags>
{
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<std::uint8_t>(*this);
}
};
// //
// structs // structs
// //
@ -432,8 +491,33 @@ struct GPUBufferRegion
}; };
static_assert(sizeof(GPUBufferRegion) == sizeof(SDL_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 GPUBufferBinding = SDL_GPUBufferBinding;
using GPUTextureSamplerBinding = SDL_GPUTextureSamplerBinding;
// //
// classes // classes
// //
@ -492,6 +576,21 @@ public:
bindVertexBuffers({&binding, 1}, offset); bindVertexBuffers({&binding, 1}, offset);
} }
void bindFragmentSamplers(std::span<const GPUTextureSamplerBinding> textureSamplerBindings, Uint32 firstSlot = 0) const noexcept
{
SDL_BindGPUFragmentSamplers(
/* render_pass = */ mHandle,
/* first_slot = */ firstSlot,
/* texture_sampler_bindings = */ textureSamplerBindings.data(),
/* num_bindings = */ static_cast<Uint32>(textureSamplerBindings.size())
);
}
void bindFragmentSampler(const GPUTextureSamplerBinding& binding, Uint32 offset = 0) const noexcept
{
bindFragmentSamplers({&binding, 1}, offset);
}
void drawPrimitives(const GPUDrawPrimitivesArgs& args) const noexcept void drawPrimitives(const GPUDrawPrimitivesArgs& args) const noexcept
{ {
SDL_DrawGPUPrimitives(mHandle, args.numVertices, args.numInstances, args.firstVertex, args.firstInstance); SDL_DrawGPUPrimitives(mHandle, args.numVertices, args.numInstances, args.firstVertex, args.firstInstance);
@ -536,26 +635,67 @@ public:
); );
} }
void uploadToGPUTexture(const GPUTextureTransferInfo& source, const GPUTextureRegion& destination, bool cycle = false)
{
SDL_UploadToGPUTexture(
/* copy_pass = */ mHandle,
/* source = */ std::bit_cast<const SDL_GPUTextureTransferInfo*>(&source),
/* destination = */ std::bit_cast<const SDL_GPUTextureRegion*>(&destination),
/* cycle = */ cycle
);
}
friend class GPUCommandBuffer; friend class GPUCommandBuffer;
}; };
class GPUTexture : public Base<SDL_GPUTexture, GPUTexture> 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<SDL_GPUTexture, GPUTexture>
{ {
private:
SDL_GPUDevice* mDevice = nullptr;
public: public:
GPUTexture() noexcept = default; GPUTexture() noexcept = default;
GPUTexture(const GPUTexture&) = delete; 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=(const GPUTexture&) = delete;
GPUTexture& operator=(GPUTexture&& other) noexcept GPUTexture& operator=(GPUTexture&& other) noexcept
{ {
Base::operator=(std::move(other)); BaseWithDevice::operator=(std::move(other));
return *this; return *this;
} }
auto operator<=>(const GPUTexture& other) const noexcept = default; 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<SDL_GPUTextureType>(args.type),
.format = static_cast<SDL_GPUTextureFormat>(args.format),
.usage = static_cast<SDL_GPUTextureUsageFlags>(args.usage),
.width = args.width,
.height = args.height,
.layer_count_or_depth = args.layerCountOrDepth,
.num_levels = args.numLevels,
.sample_count = static_cast<SDL_GPUSampleCount>(args.sampleCount)
};
mHandle = SDL_CreateGPUTexture(device, &createInfo);
if (mHandle == nullptr)
{
throw SDLError();
}
mDevice = device;
}
void destroy() noexcept void destroy() noexcept
{ {
if (mHandle != nullptr) if (mHandle != nullptr)
@ -573,6 +713,75 @@ public:
friend class GPUCommandBuffer; 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<SDL_GPUSampler, GPUSampler>
{
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<SDL_GPUFilter>(args.minFilter),
.mag_filter = static_cast<SDL_GPUFilter>(args.magFilter),
.mipmap_mode = static_cast<SDL_GPUSamplerMipmapMode>(args.mipmapMode),
.address_mode_u = static_cast<SDL_GPUSamplerAddressMode>(args.addressModeU),
.address_mode_v = static_cast<SDL_GPUSamplerAddressMode>(args.addressModeV),
.address_mode_w = static_cast<SDL_GPUSamplerAddressMode>(args.addressModeW),
.mip_lod_bias = args.mipLodBias,
.max_anisotropy = args.maxAnisotropy,
.enable_anisotropy = args.enableAnisotropy,
.enable_compare = args.enableCompare,
.compare_op = static_cast<SDL_GPUCompareOp>(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 struct GPUBeginRenderPassArgs
{ {
std::span<const GPUColorTargetInfo> colorTargetInfos; std::span<const GPUColorTargetInfo> colorTargetInfos;
@ -697,6 +906,28 @@ public:
} }
} }
[[nodiscard]]
bool windowSupportsSwapchainComposition(SDL_Window* window, GPUSwapchainComposition swapchainComposition) const noexcept
{
return SDL_WindowSupportsGPUSwapchainComposition(mHandle, window, static_cast<SDL_GPUSwapchainComposition>(swapchainComposition));
}
[[nodiscard]]
bool windowSupportsPresentMode(SDL_Window* window, GPUPresentMode presentMode) const noexcept
{
return SDL_WindowSupportsGPUPresentMode(mHandle, window, static_cast<SDL_GPUPresentMode>(presentMode));
}
void setSwapchainParameters(SDL_Window* window, GPUSwapchainComposition swapchainComposition,
GPUPresentMode presentMode) const
{
if (!SDL_SetGPUSwapchainParameters(mHandle, window, static_cast<SDL_GPUSwapchainComposition>(swapchainComposition),
static_cast<SDL_GPUPresentMode>(presentMode)))
{
throw SDLError();
}
}
[[nodiscard]] [[nodiscard]]
GPUTextureFormat getSwapchainTextureFormat(SDL_Window* window) const GPUTextureFormat getSwapchainTextureFormat(SDL_Window* window) const
{ {
@ -724,20 +955,17 @@ struct GPUGraphicsPipelineCreateArgs
GPUGraphicsPipelineTargetInfo targetInfo; GPUGraphicsPipelineTargetInfo targetInfo;
}; };
class GPUGraphicsPipeline : public Base<SDL_GPUGraphicsPipeline, GPUGraphicsPipeline> class GPUGraphicsPipeline : public BaseWithDevice<SDL_GPUGraphicsPipeline, GPUGraphicsPipeline>
{ {
private:
SDL_GPUDevice* mDevice = nullptr;
public: public:
GPUGraphicsPipeline() noexcept = default; GPUGraphicsPipeline() noexcept = default;
GPUGraphicsPipeline(const GPUGraphicsPipeline&) = delete; 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=(const GPUGraphicsPipeline&) = delete;
GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept
{ {
Base::operator=(std::move(other)); BaseWithDevice::operator=(std::move(other));
mDevice = other.mDevice;
return *this; return *this;
} }
auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default; auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default;
@ -813,19 +1041,17 @@ struct GPUShaderCreateArgs
Uint32 numUniformBuffers = 0; Uint32 numUniformBuffers = 0;
}; };
class GPUShader : public Base<SDL_GPUShader, GPUShader> class GPUShader : public BaseWithDevice<SDL_GPUShader, GPUShader>
{ {
private:
SDL_GPUDevice* mDevice = nullptr;
public: public:
GPUShader() noexcept = default; GPUShader() noexcept = default;
GPUShader(const GPUShader&) = delete; 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=(const GPUShader&) = delete;
GPUShader& operator=(GPUShader&& other) noexcept GPUShader& operator=(GPUShader&& other) noexcept
{ {
Base::operator=(std::move(other)); BaseWithDevice::operator=(std::move(other));
return *this; return *this;
} }
auto operator<=>(const GPUShader& other) const noexcept = default; auto operator<=>(const GPUShader& other) const noexcept = default;
@ -870,19 +1096,17 @@ struct GPUBufferCreateArgs
Uint32 size; Uint32 size;
}; };
class GPUBuffer : public Base<SDL_GPUBuffer, GPUBuffer> class GPUBuffer : public BaseWithDevice<SDL_GPUBuffer, GPUBuffer>
{ {
private:
SDL_GPUDevice* mDevice = nullptr;
public: public:
GPUBuffer() noexcept = default; GPUBuffer() noexcept = default;
GPUBuffer(const GPUBuffer&) = delete; 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=(const GPUBuffer&) = delete;
GPUBuffer& operator=(GPUBuffer&& other) noexcept GPUBuffer& operator=(GPUBuffer&& other) noexcept
{ {
Base::operator=(std::move(other)); BaseWithDevice::operator=(std::move(other));
return *this; return *this;
} }
auto operator<=>(const GPUBuffer& other) const noexcept = default; auto operator<=>(const GPUBuffer& other) const noexcept = default;
@ -920,19 +1144,17 @@ struct GPUTransferBufferCreateArgs
Uint32 size; Uint32 size;
}; };
class GPUTransferBuffer : public Base<SDL_GPUTransferBuffer, GPUTransferBuffer> class GPUTransferBuffer : public BaseWithDevice<SDL_GPUTransferBuffer, GPUTransferBuffer>
{ {
private:
SDL_GPUDevice* mDevice = nullptr;
public: public:
GPUTransferBuffer() noexcept = default; GPUTransferBuffer() noexcept = default;
GPUTransferBuffer(const GPUTransferBuffer&) = delete; 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=(const GPUTransferBuffer&) = delete;
GPUTransferBuffer& operator=(GPUTransferBuffer&& other) noexcept GPUTransferBuffer& operator=(GPUTransferBuffer&& other) noexcept
{ {
Base::operator=(std::move(other)); BaseWithDevice::operator=(std::move(other));
return *this; return *this;
} }
auto operator<=>(const GPUTransferBuffer& other) const noexcept = default; auto operator<=>(const GPUTransferBuffer& other) const noexcept = default;

View File

@ -13,7 +13,7 @@ struct WindowFlags : mijin::BitFlags<WindowFlags>
bool fullscreen : 1; // 0x0000000000000001 /**< window is in fullscreen mode */ bool fullscreen : 1; // 0x0000000000000001 /**< window is in fullscreen mode */
bool opengl : 1; // 0x0000000000000002 /**< window usable with OpenGL context */ bool opengl : 1; // 0x0000000000000002 /**< window usable with OpenGL context */
bool occluded : 1; // 0x0000000000000004 /**< window is occluded */ 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 borderless : 1; // 0x0000000000000010 /**< no window decoration */
bool resizable : 1; // 0x0000000000000020 /**< window can be resized */ bool resizable : 1; // 0x0000000000000020 /**< window can be resized */
bool minimized : 1; // 0x0000000000000040 /**< window is minimized */ bool minimized : 1; // 0x0000000000000040 /**< window is minimized */
@ -24,7 +24,7 @@ struct WindowFlags : mijin::BitFlags<WindowFlags>
bool external : 1; // 0x0000000000000800 /**< window not created by SDL */ bool external : 1; // 0x0000000000000800 /**< window not created by SDL */
bool modal : 1; // 0x0000000000001000 /**< window is modal */ bool modal : 1; // 0x0000000000001000 /**< window is modal */
bool high_pixel_density : 1; // 0x0000000000002000 /**< window uses high pixel density back buffer if possible */ 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 mouse_relative_mode : 1; // 0x0000000000008000 /**< window has relative mode enabled */
bool always_on_top : 1; // 0x0000000000010000 /**< window should always be above others */ 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 */ bool utility : 1; // 0x0000000000020000 /**< window should be treated as a utility window, not showing in the task bar and window list */

View File

@ -0,0 +1,78 @@
#include "./bitmap.hpp"
#include <cstring>
#include <format>
#define STBI_ASSERT(x) MIJIN_ASSERT(x, #x)
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#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<mijin::Stream*>(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<int>(bytesRead);
}
void stbiSkipCallback(void* user, int bytes)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
(void) stream.seek(bytes, mijin::SeekMode::RELATIVE);
}
int stbiEofCallback(void* user)
{
mijin::Stream& stream = *static_cast<mijin::Stream*>(user);
return stream.isAtEnd();
}
}
Bitmap loadBitmap(const mijin::PathReference& path)
{
std::unique_ptr<mijin::Stream> 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<Pixel> pixels;
pixels.resize(static_cast<std::size_t>(width) * height);
std::memcpy(pixels.data(), data, pixels.size() * sizeof(Pixel));
return {
.width = static_cast<unsigned>(width),
.height = static_cast<unsigned>(height),
.pixels = std::move(pixels)
};
}
}

View File

@ -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 <cstdint>
#include <vector>
#include <mijin/virtual_filesystem/filesystem.hpp>
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<Pixel> 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)