commit 2d531dafa991311abec306650a3a2d0d2773e5dd Author: Patrick Wuttke Date: Fri Jan 31 17:16:39 2025 +0100 Initial commit. diff --git a/SModule b/SModule new file mode 100644 index 0000000..6a8d9ec --- /dev/null +++ b/SModule @@ -0,0 +1,8 @@ + +Import('env') + +LIB_CONFIG = { + 'CPPPATH': [env.Dir('public')] +} + +Return('LIB_CONFIG') diff --git a/public/sdlpp/common.hpp b/public/sdlpp/common.hpp new file mode 100644 index 0000000..9060ed2 --- /dev/null +++ b/public/sdlpp/common.hpp @@ -0,0 +1,94 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_COMMON_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_COMMON_HPP_INCLUDED 1 + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace sdlpp +{ +template +class Base +{ +protected: + THandle* mHandle = nullptr; +protected: + Base() noexcept = default; + explicit Base(THandle* handle) noexcept : mHandle(handle) {} + Base(const Base&) noexcept = default; + Base(Base&& other) noexcept : mHandle(std::exchange(other.mHandle, nullptr)) {} + + Base& operator=(const Base&) noexcept = default; + Base& operator=(Base&& other) noexcept + { + if (this != &other) + { + static_cast(*this).destroy(); + mHandle = std::exchange(other.mHandle, nullptr); + } + return *this; + } +public: + ~Base() noexcept + { + static_cast(*this).destroy(); + } + auto operator<=>(const Base& other) const noexcept = default; + + operator THandle*() const noexcept { return mHandle; } + + explicit operator bool() const noexcept + { + return mHandle != nullptr; + } + bool operator!() const noexcept + { + return mHandle == nullptr; + } +}; + +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: + SDLError() : std::runtime_error(SDL_GetError()) {} +}; +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_COMMON_HPP_INCLUDED) diff --git a/public/sdlpp/event.hpp b/public/sdlpp/event.hpp new file mode 100644 index 0000000..acc62ea --- /dev/null +++ b/public/sdlpp/event.hpp @@ -0,0 +1,145 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_EVENT_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_EVENT_HPP_INCLUDED 1 + +#include +#include + +#include "./common.hpp" + +namespace sdlpp +{ +// +// flags +// +struct MouseButtonFlags : mijin::BitFlags +{ + bool left : 1 = false; + bool middle : 1 = false; + bool right : 1 = false; + bool x1 : 1 = false; + bool x2 : 1 = false; + + static MouseButtonFlags from(SDL_MouseButtonFlags base) noexcept + { + return std::bit_cast(static_cast(base)); + } +}; + +// +// structs +// +struct Event +{ + Uint64 timestamp; + + template + explicit Event(const TBase& base) noexcept : timestamp(base.timestamp) {} +}; + +struct KeyboardEvent : Event +{ + SDL_WindowID windowID; + SDL_KeyboardID which; + SDL_Scancode scancode; + SDL_Keycode key; + SDL_Keymod mod; + bool down; + bool repeat; + + explicit KeyboardEvent(const SDL_KeyboardEvent& base) noexcept : Event(base), windowID(base.windowID), + which(base.which), scancode(base.scancode), key(base.key), mod(base.mod), down(base.down), repeat(base.repeat) + { + } +}; + +struct MouseButtonEvent : Event +{ + SDL_WindowID windowID; + SDL_MouseID which; + Uint8 button; + bool down; + Uint8 clicks; + float x; + float y; + + MouseButtonEvent(const SDL_MouseButtonEvent& base) noexcept : Event(base), windowID(base.windowID), which(base.which), + button(base.button), down(base.down), clicks(base.clicks), x(base.x), y(base.y) + { + } +}; + +struct MouseMotionEvent : Event +{ + SDL_WindowID windowID; + SDL_MouseID which; + MouseButtonFlags state; + float x; + float y; + float xrel; + float yrel; + + MouseMotionEvent(const SDL_MouseMotionEvent& base) noexcept : Event(base), windowID(base.windowID), which(base.which), + state(MouseButtonFlags::from(base.state)), x(base.x), y(base.y), xrel(base.xrel), yrel(base.yrel) + { + } +}; + +struct GamepadButtonEvent : Event +{ + SDL_JoystickID which; + Uint8 button; + bool down; + + GamepadButtonEvent(const SDL_GamepadButtonEvent& base) noexcept : Event(base), which(base.which), button(base.button), + down(base.down) + { + } +}; + +struct QuitEvent : Event +{ + explicit QuitEvent(const SDL_QuitEvent& base) noexcept : Event(base) {} +}; + +using sdl_event_t = std::variant< + KeyboardEvent, + MouseButtonEvent, + MouseMotionEvent, + GamepadButtonEvent, + QuitEvent +>; + +[[nodiscard]] +inline std::optional pollEvent() noexcept +{ + SDL_Event event; + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_EVENT_KEY_DOWN: + case SDL_EVENT_KEY_UP: + return KeyboardEvent(event.key); + case SDL_EVENT_MOUSE_BUTTON_DOWN: + case SDL_EVENT_MOUSE_BUTTON_UP: + return MouseButtonEvent(event.button); + case SDL_EVENT_MOUSE_MOTION: + return MouseMotionEvent(event.motion); + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + case SDL_EVENT_GAMEPAD_BUTTON_UP: + return GamepadButtonEvent(event.gbutton); + case SDL_EVENT_QUIT: + return QuitEvent(event.quit); + default: + // can't translate this yet + break; + } + } + return std::nullopt; +} +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_EVENT_HPP_INCLUDED) diff --git a/public/sdlpp/gamepad.hpp b/public/sdlpp/gamepad.hpp new file mode 100644 index 0000000..3f4e755 --- /dev/null +++ b/public/sdlpp/gamepad.hpp @@ -0,0 +1,68 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GAMEPAD_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GAMEPAD_HPP_INCLUDED 1 + +#include "./common.hpp" + +namespace sdlpp +{ +enum class GamepadAxis +{ + INVALID = SDL_GAMEPAD_AXIS_INVALID, + LEFTX = SDL_GAMEPAD_AXIS_LEFTX, + LEFTY = SDL_GAMEPAD_AXIS_LEFTY, + RIGHTX = SDL_GAMEPAD_AXIS_RIGHTX, + RIGHTY = SDL_GAMEPAD_AXIS_RIGHTY, + LEFT_TRIGGER = SDL_GAMEPAD_AXIS_LEFT_TRIGGER, + RIGHT_TRIGGER = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, +}; + +class Gamepad : public Base +{ +public: + Gamepad() noexcept = default; + Gamepad(Gamepad&&) noexcept = default; + + Gamepad& operator=(Gamepad&&) = default; + + void open(SDL_JoystickID instanceID) + { + MIJIN_ASSERT(mHandle == nullptr, "Gamepad already opened."); + mHandle = SDL_OpenGamepad(instanceID); + if (mHandle == nullptr) + { + throw SDLError(); + } + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_CloseGamepad(mHandle); + mHandle = nullptr; + } + } + + [[nodiscard]] + Sint16 getAxis(GamepadAxis axis) const noexcept + { + return SDL_GetGamepadAxis(mHandle, static_cast(axis)); + } +}; + +[[nodiscard]] +inline std::vector getGamepads() noexcept +{ + int count = 0; + SDL_JoystickID* gamepads = SDL_GetGamepads(&count); + std::vector result; + result.resize(count); + std::copy_n(gamepads, count, result.begin()); + return result; +} +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GAMEPAD_HPP_INCLUDED) diff --git a/public/sdlpp/gpu.hpp b/public/sdlpp/gpu.hpp new file mode 100644 index 0000000..43c0022 --- /dev/null +++ b/public/sdlpp/gpu.hpp @@ -0,0 +1,1210 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED 1 + +#include "./common.hpp" + +namespace sdlpp +{ +static_assert(sizeof(bool) == sizeof(bool)); // we assume this in the whole file... + +// +// enums +// +enum class GPUVertexInputRate +{ + VERTEX = SDL_GPU_VERTEXINPUTRATE_VERTEX, + INSTANCE = SDL_GPU_VERTEXINPUTRATE_INSTANCE +}; + +enum class GPUVertexElementFormat +{ + INT = SDL_GPU_VERTEXELEMENTFORMAT_INT, + INT2 = SDL_GPU_VERTEXELEMENTFORMAT_INT2, + INT3 = SDL_GPU_VERTEXELEMENTFORMAT_INT3, + INT4 = SDL_GPU_VERTEXELEMENTFORMAT_INT4, + UINT = SDL_GPU_VERTEXELEMENTFORMAT_UINT, + UINT2 = SDL_GPU_VERTEXELEMENTFORMAT_UINT2, + UINT3 = SDL_GPU_VERTEXELEMENTFORMAT_UINT3, + UINT4 = SDL_GPU_VERTEXELEMENTFORMAT_UINT4, + FLOAT = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT, + FLOAT2 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, + FLOAT3 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, + FLOAT4 = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT4, + BYTE2 = SDL_GPU_VERTEXELEMENTFORMAT_BYTE2, + BYTE4 = SDL_GPU_VERTEXELEMENTFORMAT_BYTE4, + UBYTE2 = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2, + UBYTE4 = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4, + BYTE2_NORM = SDL_GPU_VERTEXELEMENTFORMAT_BYTE2_NORM, + BYTE4_NORM = SDL_GPU_VERTEXELEMENTFORMAT_BYTE4_NORM, + UBYTE2_NORM = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE2_NORM, + UBYTE4_NORM = SDL_GPU_VERTEXELEMENTFORMAT_UBYTE4_NORM, + SHORT2 = SDL_GPU_VERTEXELEMENTFORMAT_SHORT2, + SHORT4 = SDL_GPU_VERTEXELEMENTFORMAT_SHORT4, + USHORT2 = SDL_GPU_VERTEXELEMENTFORMAT_USHORT2, + USHORT4 = SDL_GPU_VERTEXELEMENTFORMAT_USHORT4, + SHORT2_NORM = SDL_GPU_VERTEXELEMENTFORMAT_SHORT2_NORM, + SHORT4_NORM = SDL_GPU_VERTEXELEMENTFORMAT_SHORT4_NORM, + USHORT2_NORM = SDL_GPU_VERTEXELEMENTFORMAT_USHORT2_NORM, + USHORT4_NORM = SDL_GPU_VERTEXELEMENTFORMAT_USHORT4_NORM, + HALF2 = SDL_GPU_VERTEXELEMENTFORMAT_HALF2, + HALF4 = SDL_GPU_VERTEXELEMENTFORMAT_HALF4 +}; + +enum class GPUPrimitiveType +{ + POINTLIST = SDL_GPU_PRIMITIVETYPE_POINTLIST, + LINELIST = SDL_GPU_PRIMITIVETYPE_LINELIST, + LINESTRIP = SDL_GPU_PRIMITIVETYPE_LINESTRIP, + TRIANGLELIST = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST, + TRIANGLESTRIP = SDL_GPU_PRIMITIVETYPE_TRIANGLESTRIP +}; + +enum class GPUFillMode +{ + FILL = SDL_GPU_FILLMODE_FILL, + LINE = SDL_GPU_FILLMODE_LINE +}; + +enum class GPUCullMode +{ + NONE = SDL_GPU_CULLMODE_NONE, + FRONT = SDL_GPU_CULLMODE_FRONT, + BACK = SDL_GPU_CULLMODE_BACK +}; + +enum class GPUFrontFace +{ + COUNTER_CLOCKWISE = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE, + CLOCKWISE = SDL_GPU_FRONTFACE_CLOCKWISE +}; + +enum class GPUSampleCount +{ + ONE = SDL_GPU_SAMPLECOUNT_1, + TWO = SDL_GPU_SAMPLECOUNT_2, + FOUR = SDL_GPU_SAMPLECOUNT_4, + EIGHT = SDL_GPU_SAMPLECOUNT_8 +}; + +enum class GPUCompareOp +{ + NEVER = SDL_GPU_COMPAREOP_NEVER, + LESS = SDL_GPU_COMPAREOP_LESS, + EQUAL = SDL_GPU_COMPAREOP_EQUAL, + LESS_OR_EQUAL = SDL_GPU_COMPAREOP_LESS_OR_EQUAL, + GREATER = SDL_GPU_COMPAREOP_GREATER, + NOT_EQUAL = SDL_GPU_COMPAREOP_NOT_EQUAL, + GREATER_OR_EQUAL = SDL_GPU_COMPAREOP_GREATER_OR_EQUAL, + ALWAYS = SDL_GPU_COMPAREOP_ALWAYS +}; + +enum class GPUStencilOp +{ + KEEP = SDL_GPU_STENCILOP_KEEP, + ZERO = SDL_GPU_STENCILOP_ZERO, + REPLACE = SDL_GPU_STENCILOP_REPLACE, + INCREMENT_AND_CLAMP = SDL_GPU_STENCILOP_INCREMENT_AND_CLAMP, + DECREMENT_AND_CLAMP = SDL_GPU_STENCILOP_DECREMENT_AND_CLAMP, + INVERT = SDL_GPU_STENCILOP_INVERT, + INCREMENT_AND_WRAP = SDL_GPU_STENCILOP_INCREMENT_AND_WRAP, + DECREMENT_AND_WRAP = SDL_GPU_STENCILOP_DECREMENT_AND_WRAP +}; + +enum class GPUBlendFactor +{ + ZERO = SDL_GPU_BLENDFACTOR_ZERO, + ONE = SDL_GPU_BLENDFACTOR_ONE, + SRC_COLOR = SDL_GPU_BLENDFACTOR_SRC_COLOR, + ONE_MINUS_SRC_COLOR = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_COLOR, + DST_COLOR = SDL_GPU_BLENDFACTOR_DST_COLOR, + ONE_MINUS_DST_COLOR = SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_COLOR, + SRC_ALPHA = SDL_GPU_BLENDFACTOR_SRC_ALPHA, + ONE_MINUS_SRC_ALPHA = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + DST_ALPHA = SDL_GPU_BLENDFACTOR_DST_ALPHA, + ONE_MINUS_DST_ALPHA = SDL_GPU_BLENDFACTOR_ONE_MINUS_DST_ALPHA, + CONSTANT_COLOR = SDL_GPU_BLENDFACTOR_CONSTANT_COLOR, + ONE_MINUS_CONSTANT_COLOR = SDL_GPU_BLENDFACTOR_ONE_MINUS_CONSTANT_COLOR, + SRC_ALPHA_SATURATE = SDL_GPU_BLENDFACTOR_SRC_ALPHA_SATURATE +}; + +enum class GPUBlendOp +{ + ADD = SDL_GPU_BLENDOP_ADD, + SUBTRACT = SDL_GPU_BLENDOP_SUBTRACT, + REVERSE_SUBTRACT = SDL_GPU_BLENDOP_REVERSE_SUBTRACT, + MIN = SDL_GPU_BLENDOP_MIN, + 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, + A8_UNORM = SDL_GPU_TEXTUREFORMAT_A8_UNORM, + R8_UNORM = SDL_GPU_TEXTUREFORMAT_R8_UNORM, + R8G8_UNORM = SDL_GPU_TEXTUREFORMAT_R8G8_UNORM, + R8G8B8A8_UNORM = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, + R16_UNORM = SDL_GPU_TEXTUREFORMAT_R16_UNORM, + R16G16_UNORM = SDL_GPU_TEXTUREFORMAT_R16G16_UNORM, + R16G16B16A16_UNORM = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UNORM, + R10G10B10A2_UNORM = SDL_GPU_TEXTUREFORMAT_R10G10B10A2_UNORM, + B5G6R5_UNORM = SDL_GPU_TEXTUREFORMAT_B5G6R5_UNORM, + B5G5R5A1_UNORM = SDL_GPU_TEXTUREFORMAT_B5G5R5A1_UNORM, + B4G4R4A4_UNORM = SDL_GPU_TEXTUREFORMAT_B4G4R4A4_UNORM, + B8G8R8A8_UNORM = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM, + BC1_RGBA_UNORM = SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM, + BC2_RGBA_UNORM = SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM, + BC3_RGBA_UNORM = SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM, + BC4_R_UNORM = SDL_GPU_TEXTUREFORMAT_BC4_R_UNORM, + BC5_RG_UNORM = SDL_GPU_TEXTUREFORMAT_BC5_RG_UNORM, + BC7_RGBA_UNORM = SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM, + BC6H_RGB_FLOAT = SDL_GPU_TEXTUREFORMAT_BC6H_RGB_FLOAT, + BC6H_RGB_UFLOAT = SDL_GPU_TEXTUREFORMAT_BC6H_RGB_UFLOAT, + R8_SNORM = SDL_GPU_TEXTUREFORMAT_R8_SNORM, + R8G8_SNORM = SDL_GPU_TEXTUREFORMAT_R8G8_SNORM, + R8G8B8A8_SNORM = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_SNORM, + R16_SNORM = SDL_GPU_TEXTUREFORMAT_R16_SNORM, + R16G16_SNORM = SDL_GPU_TEXTUREFORMAT_R16G16_SNORM, + R16G16B16A16_SNORM = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_SNORM, + R16_FLOAT = SDL_GPU_TEXTUREFORMAT_R16_FLOAT, + R16G16_FLOAT = SDL_GPU_TEXTUREFORMAT_R16G16_FLOAT, + R16G16B16A16_FLOAT = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT, + R32_FLOAT = SDL_GPU_TEXTUREFORMAT_R32_FLOAT, + R32G32_FLOAT = SDL_GPU_TEXTUREFORMAT_R32G32_FLOAT, + R32G32B32A32_FLOAT = SDL_GPU_TEXTUREFORMAT_R32G32B32A32_FLOAT, + R11G11B10_UFLOAT = SDL_GPU_TEXTUREFORMAT_R11G11B10_UFLOAT, + R8_UINT = SDL_GPU_TEXTUREFORMAT_R8_UINT, + R8G8_UINT = SDL_GPU_TEXTUREFORMAT_R8G8_UINT, + R8G8B8A8_UINT = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UINT, + R16_UINT = SDL_GPU_TEXTUREFORMAT_R16_UINT, + R16G16_UINT = SDL_GPU_TEXTUREFORMAT_R16G16_UINT, + R16G16B16A16_UINT = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_UINT, + R8_INT = SDL_GPU_TEXTUREFORMAT_R8_INT, + R8G8_INT = SDL_GPU_TEXTUREFORMAT_R8G8_INT, + R8G8B8A8_INT = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_INT, + R16_INT = SDL_GPU_TEXTUREFORMAT_R16_INT, + R16G16_INT = SDL_GPU_TEXTUREFORMAT_R16G16_INT, + R16G16B16A16_INT = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_INT, + R8G8B8A8_UNORM_SRGB = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB, + B8G8R8A8_UNORM_SRGB = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB, + BC1_RGBA_UNORM_SRGB = SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB, + BC2_RGBA_UNORM_SRGB = SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB, + BC3_RGBA_UNORM_SRGB = SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB, + BC7_RGBA_UNORM_SRGB = SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB, + D16_UNORM = SDL_GPU_TEXTUREFORMAT_D16_UNORM, + D24_UNORM = SDL_GPU_TEXTUREFORMAT_D24_UNORM, + D32_FLOAT = SDL_GPU_TEXTUREFORMAT_D32_FLOAT, + D24_UNORM_S8_UINT = SDL_GPU_TEXTUREFORMAT_D24_UNORM_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 +{ + PRIVATE = SDL_GPU_SHADERFORMAT_PRIVATE, + SPIRV = SDL_GPU_SHADERFORMAT_SPIRV, + DXBC = SDL_GPU_SHADERFORMAT_DXBC, + DXIL = SDL_GPU_SHADERFORMAT_DXIL, + MSL = SDL_GPU_SHADERFORMAT_MSL, + METALLIB = SDL_GPU_SHADERFORMAT_METALLIB +}; + +enum class GPUShaderStage +{ + VERTEX = SDL_GPU_SHADERSTAGE_VERTEX, + FRAGMENT = SDL_GPU_SHADERSTAGE_FRAGMENT +}; + +enum class GPULoadOp +{ + LOAD = SDL_GPU_LOADOP_LOAD, + CLEAR = SDL_GPU_LOADOP_CLEAR, + DONT_CARE = SDL_GPU_LOADOP_DONT_CARE +}; + +enum class GPUStoreOp +{ + STORE = SDL_GPU_STOREOP_STORE, + DONT_CARE = SDL_GPU_STOREOP_DONT_CARE +}; + +enum class GPUTransferBufferUsage +{ + UPLOAD = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, + 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 +// +struct GPUShaderFormatFlags : mijin::BitFlags +{ + bool private_ : 1 = false; + bool spirv : 1 = false; + bool dxbc : 1 = false; + bool dxil : 1 = false; + bool msl : 1 = false; + bool metallib : 1 = false; + + constexpr operator SDL_GPUShaderFormat() const noexcept + { + return std::bit_cast(*this); + } +}; + +struct GPUColorComponentFlags : mijin::BitFlags +{ + bool r : 1 = false; + bool g : 1 = false; + bool b : 1 = false; + bool a : 1 = false; +}; + +struct GPUBufferUsageFlags : mijin::BitFlags +{ + bool vertex : 1 = false; + bool index : 1 = false; + bool indirect : 1 = false; + bool graphicsStorageRead : 1 = false; + bool computeStorageRead : 1 = false; + bool computeStorageWrite : 1 = false; + + explicit operator SDL_GPUBufferUsageFlags() const noexcept + { + return std::bit_cast(*this); + } +}; + +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 +// +struct GPUVertexBinding +{ + Uint32 index; + Uint32 pitch; + GPUVertexInputRate inputRate = GPUVertexInputRate::VERTEX; + Uint32 instanceStepRate; +}; +static_assert(sizeof(GPUVertexBinding) == sizeof(SDL_GPUVertexBinding) + && alignof(GPUVertexBinding) == alignof(SDL_GPUVertexBinding)); + +struct GPUVertexAttribute +{ + Uint32 location; + Uint32 bindingIndex; + GPUVertexElementFormat format; + Uint32 offset; +}; +static_assert(sizeof(GPUVertexAttribute) == sizeof(SDL_GPUVertexAttribute) + && alignof(GPUVertexAttribute) == alignof(SDL_GPUVertexAttribute)); + +struct GPUVertexInputState +{ + std::span vertexBindings; + std::span vertexAttributes; +}; + +struct GPURasterizerState +{ + GPUFillMode fillMode = GPUFillMode::FILL; + GPUCullMode cullMode = GPUCullMode::NONE; + GPUFrontFace frontFace = GPUFrontFace::COUNTER_CLOCKWISE; + bool enableDepthBias = false; + float depthBiasConstantFactor; + float depthBiasClamp; + float depthBiasSlopeFactor; +}; + +struct GPUMultisampleState +{ + GPUSampleCount sampleCount = GPUSampleCount::ONE; + Uint32 sampleMask = 0xFFFFFFFF; +}; + +struct GPUStencilOpState +{ + GPUStencilOp failOp; + GPUStencilOp passOp; + GPUStencilOp depthFailOp; + GPUCompareOp compareOp; + + explicit operator SDL_GPUStencilOpState() const noexcept + { + return { + .fail_op = static_cast(failOp), + .pass_op = static_cast(passOp), + .depth_fail_op = static_cast(depthFailOp), + .compare_op = static_cast(compareOp), + }; + } +}; + +struct GPUDepthStencilState +{ + bool enableDepthTest = false; + bool enableDepthWrite = false; + bool enableStencilTest = false; + GPUCompareOp compareOp = GPUCompareOp::LESS_OR_EQUAL; + GPUStencilOpState backStencilState; + GPUStencilOpState frontStencilState; + Uint8 compareMask; + Uint8 writeMask; +}; + +struct GPUColorTargetBlendState +{ + bool enableBlend = false; + GPUBlendFactor srcColorBlendfactor; + GPUBlendFactor dstColorBlendfactor; + GPUBlendOp colorBlendOp; + GPUBlendFactor srcAlphaBlendfactor; + GPUBlendFactor dstAlphaBlendfactor; + GPUBlendOp alphaBlendOp; + GPUColorComponentFlags colorWriteMask = {.r = true, .g = true, .b = true, .a = true}; +}; + +struct GPUColorTargetDescription +{ + GPUTextureFormat format = GPUTextureFormat::INVALID; + GPUColorTargetBlendState blendState; +}; + +static_assert(sizeof(GPUColorTargetDescription) == sizeof(SDL_GPUColorTargetDescription) + && alignof(GPUColorTargetDescription) == alignof(SDL_GPUColorTargetDescription)); + +struct GPUGraphicsPipelineTargetInfo +{ + std::span colorTargetDescriptions; + GPUTextureFormat depthStencilFormat = GPUTextureFormat::INVALID; + + explicit operator SDL_GpuGraphicsPipelineTargetInfo() const noexcept + { + return { + .color_target_descriptions = std::bit_cast(colorTargetDescriptions.data()), + .num_color_targets = static_cast(colorTargetDescriptions.size()), + .has_depth_stencil_target = depthStencilFormat != GPUTextureFormat::INVALID, + .depth_stencil_format = static_cast(depthStencilFormat) + }; + } +}; + +using FColor = SDL_FColor; + +struct GPUColorTargetInfo +{ + SDL_GPUTexture* texture = nullptr; + Uint32 mipLevel = 0; + Uint32 layerOrDepthPlane = 0; + FColor clearColor = {.r = 0, .g = 0, .b = 0, .a = 1}; + GPULoadOp loadOp = GPULoadOp::LOAD; + GPUStoreOp storeOp = GPUStoreOp::STORE; + bool cycle = false; +}; +static_assert(sizeof(GPUColorTargetInfo) == sizeof(SDL_GPUColorTargetInfo) + && alignof(GPUColorTargetInfo) == alignof(SDL_GPUColorTargetInfo)); + +struct GPUDepthStencilTargetInfo +{ + SDL_GPUTexture* texture = nullptr; + float clearDepth = 1.f; + GPULoadOp loadOp = GPULoadOp::LOAD; + GPUStoreOp storeOp = GPUStoreOp::STORE; + GPULoadOp stencilLoadOp = GPULoadOp::LOAD; + GPUStoreOp stencilStoreOp = GPUStoreOp::STORE; + bool cycle = true; + Uint8 clearStencil; +}; +static_assert(sizeof(GPUDepthStencilTargetInfo) == sizeof(SDL_GPUDepthStencilTargetInfo) + && alignof(GPUDepthStencilTargetInfo) == alignof(SDL_GPUDepthStencilTargetInfo)); + +struct GPUTransferBufferLocation +{ + SDL_GPUTransferBuffer* transferBuffer; + Uint32 offset = 0; +}; +static_assert(sizeof(GPUTransferBufferLocation) == sizeof(SDL_GPUTransferBufferLocation)); + +struct GPUBufferRegion +{ + SDL_GPUBuffer* buffer; + Uint32 offset = 0; + Uint32 size; +}; +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 +// + +struct GPUDrawPrimitivesArgs +{ + Uint32 numVertices = 0; + Uint32 numInstances = 1; + Uint32 firstVertex = 0; + Uint32 firstInstance = 0; +}; + +class GPURenderPass : public Base +{ +public: + GPURenderPass() noexcept = default; + GPURenderPass(const GPURenderPass&) = delete; + GPURenderPass(GPURenderPass&& other) noexcept : Base(std::move(other)) {} + + GPURenderPass& operator=(const GPURenderPass&) = delete; + GPURenderPass& operator=(GPURenderPass&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPURenderPass& other) const noexcept = default; + + void end() noexcept + { + SDL_EndGPURenderPass(mHandle); + mHandle = nullptr; + } + + void destroy() noexcept + { + MIJIN_ASSERT(mHandle == nullptr, "Renderpass has not been ended."); + } + + void bindGraphicsPipeline(SDL_GPUGraphicsPipeline* pipeline) const noexcept + { + SDL_BindGPUGraphicsPipeline(mHandle, pipeline); + } + + void bindVertexBuffers(std::span bindings, Uint32 firstBinding = 0) const noexcept + { + SDL_BindGPUVertexBuffers( + /* render_pass = */ mHandle, + /* first_binding = */ firstBinding, + /* bindings = */ bindings.data(), + /* num_bindings = */ static_cast(bindings.size()) + ); + } + + void bindVertexBuffer(const GPUBufferBinding& binding, Uint32 offset = 0) const noexcept + { + 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); + } + + friend class GPUCommandBuffer; +}; + +class GPUCopyPass : public Base +{ +public: + GPUCopyPass() noexcept = default; + GPUCopyPass(const GPUCopyPass&) = delete; + GPUCopyPass(GPUCopyPass&& other) noexcept : Base(std::move(other)) {} + + GPUCopyPass& operator=(const GPUCopyPass&) = delete; + GPUCopyPass& operator=(GPUCopyPass&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUCopyPass& other) const noexcept = default; + + void end() noexcept + { + SDL_EndGPUCopyPass(mHandle); + mHandle = nullptr; + } + + void destroy() noexcept + { + MIJIN_ASSERT(mHandle == nullptr, "Copypass has not been ended."); + } + + 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 + ); + } + + 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; +}; + +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 +{ +public: + GPUTexture() noexcept = default; + GPUTexture(const GPUTexture&) = delete; + GPUTexture(GPUTexture&& other) noexcept : BaseWithDevice(std::move(other)) {} + + GPUTexture& operator=(const GPUTexture&) = delete; + GPUTexture& operator=(GPUTexture&& other) noexcept + { + 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) + { + // if this is not manually created (e.g. a swapchain image), device will be nullptr + if (mDevice != nullptr) + { + SDL_ReleaseGPUTexture(mDevice, mHandle); + } + mHandle = nullptr; + mDevice = nullptr; + } + } + + 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; + std::optional depthStencilTargetInfo; +}; + +class GPUCommandBuffer : public Base +{ +public: + GPUCommandBuffer() noexcept = default; + GPUCommandBuffer(const GPUCommandBuffer&) = delete; + GPUCommandBuffer(GPUCommandBuffer&& other) noexcept : Base(std::move(other)) {} + + GPUCommandBuffer& operator=(const GPUCommandBuffer&) = delete; + GPUCommandBuffer& operator=(GPUCommandBuffer&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUCommandBuffer& other) const noexcept = default; + + void submit() noexcept + { + SDL_SubmitGPUCommandBuffer(mHandle); + mHandle = nullptr; + } + + void destroy() noexcept + { + MIJIN_ASSERT(mHandle == nullptr, "Command buffer has not been submitted."); + } + + [[nodiscard]] + GPURenderPass beginRenderPass(const GPUBeginRenderPassArgs& args) const noexcept + { + GPURenderPass renderPass; + renderPass.mHandle = SDL_BeginGPURenderPass( + /* command_buffer = */ mHandle, + /* color_target_infos = */ std::bit_cast(args.colorTargetInfos.data()), + /* num_color_targets = */ static_cast(args.colorTargetInfos.size()), + /* depth_stencil_target_info = */ args.depthStencilTargetInfo.has_value() ? std::bit_cast(&*args.depthStencilTargetInfo) : nullptr + ); + return renderPass; + } + + [[nodiscard]] + GPUCopyPass beginCopyPass() const noexcept + { + GPUCopyPass copyPass; + copyPass.mHandle = SDL_BeginGPUCopyPass(mHandle); + return copyPass; + } + + [[nodiscard]] + GPUTexture acquireSwapchainTexture(SDL_Window* window, Uint32& outWidth, Uint32& outHeight) noexcept + { + GPUTexture texture; + texture.mHandle = SDL_AcquireGPUSwapchainTexture(mHandle, window, &outWidth, &outHeight); + return texture; + } + + template + void pushVertexUniformData(Uint32 slotIndex, std::span data) const noexcept + { + SDL_PushGPUVertexUniformData( + /* command_buffer = */ mHandle, + /* slot_index = */ slotIndex, + /* data = */ data.data(), + /* length = */ static_cast(data.size_bytes()) + ); + } + + template + void pushFragmentUniformData(Uint32 slotIndex, std::span data) const noexcept + { + SDL_PushGPUFragmentUniformData( + /* command_buffer = */ mHandle, + /* slot_index = */ slotIndex, + /* data = */ data.data(), + /* length = */ static_cast(data.size_bytes()) + ); + } + + friend class GPUDevice; +}; + +struct GPUDeviceCreateArgs +{ + GPUShaderFormatFlags formatFlags = {}; + bool debugMode = false; + const char* name = nullptr; +}; + +class GPUDevice : public Base +{ +public: + GPUDevice() noexcept = default; + GPUDevice(const GPUDevice&) = delete; + GPUDevice(GPUDevice&& other) noexcept : Base(std::move(other)) {} + + GPUDevice& operator=(const GPUDevice&) = delete; + GPUDevice& operator=(GPUDevice&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUDevice& other) const noexcept = default; + + void create(const GPUDeviceCreateArgs& args = {}) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUDevice has already been created."); + mHandle = SDL_CreateGPUDevice(args.formatFlags, args.debugMode, args.name); + if (mHandle == nullptr) + { + throw SDLError(); + } + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_DestroyGPUDevice(mHandle); + mHandle = nullptr; + } + } + + void claimWindow(SDL_Window* window) const + { + if (!SDL_ClaimWindowForGPUDevice(mHandle, window)) + { + throw SDLError(); + } + } + + [[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 + { + return static_cast(SDL_GetGPUSwapchainTextureFormat(mHandle, window)); + } + + [[nodiscard]] + GPUCommandBuffer acquireCommandBuffer() const noexcept + { + GPUCommandBuffer cmdBuffer; + cmdBuffer.mHandle = SDL_AcquireGPUCommandBuffer(mHandle); + return cmdBuffer; + } +}; + +struct GPUGraphicsPipelineCreateArgs +{ + SDL_GPUShader* vertexShader; + SDL_GPUShader* fragmentShader; + GPUVertexInputState vertexInputState; + GPUPrimitiveType primitiveType = GPUPrimitiveType::TRIANGLELIST; + GPURasterizerState rasterizerState; + GPUMultisampleState multisampleState; + GPUDepthStencilState depthStencilState; + GPUGraphicsPipelineTargetInfo targetInfo; +}; + +class GPUGraphicsPipeline : public BaseWithDevice +{ +public: + GPUGraphicsPipeline() noexcept = default; + GPUGraphicsPipeline(const GPUGraphicsPipeline&) = delete; + GPUGraphicsPipeline(GPUGraphicsPipeline&& other) noexcept : BaseWithDevice(std::move(other)) {} + + GPUGraphicsPipeline& operator=(const GPUGraphicsPipeline&) = delete; + GPUGraphicsPipeline& operator=(GPUGraphicsPipeline&& other) noexcept + { + BaseWithDevice::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUGraphicsPipeline& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUGraphicsPipelineCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUGraphicsPipeline has already been created."); + const SDL_GPUGraphicsPipelineCreateInfo createInfo = + { + .vertex_shader = args.vertexShader, + .fragment_shader = args.fragmentShader, + .vertex_input_state = { + .vertex_bindings = std::bit_cast(args.vertexInputState.vertexBindings.data()), + .num_vertex_bindings = static_cast(args.vertexInputState.vertexBindings.size()), + .vertex_attributes = std::bit_cast(args.vertexInputState.vertexAttributes.data()), + .num_vertex_attributes = static_cast(args.vertexInputState.vertexAttributes.size()) + }, + .primitive_type = static_cast(args.primitiveType), + .rasterizer_state = { + .fill_mode = static_cast(args.rasterizerState.fillMode), + .cull_mode = static_cast(args.rasterizerState.cullMode), + .front_face = static_cast(args.rasterizerState.frontFace), + .enable_depth_bias = args.rasterizerState.enableDepthBias, + .depth_bias_constant_factor = args.rasterizerState.depthBiasConstantFactor, + .depth_bias_clamp = args.rasterizerState.depthBiasClamp, + .depth_bias_slope_factor = args.rasterizerState.depthBiasSlopeFactor + }, + .multisample_state = { + .sample_count = static_cast(args.multisampleState.sampleCount), + .sample_mask = args.multisampleState.sampleMask + }, + .depth_stencil_state = { + .enable_depth_test = args.depthStencilState.enableDepthTest, + .enable_depth_write = args.depthStencilState.enableDepthWrite, + .enable_stencil_test = args.depthStencilState.enableStencilTest, + .compare_op = static_cast(args.depthStencilState.compareOp), + .back_stencil_state = static_cast(args.depthStencilState.backStencilState), + .front_stencil_state = static_cast(args.depthStencilState.frontStencilState), + .compare_mask = args.depthStencilState.compareMask, + .write_mask = args.depthStencilState.writeMask + }, + .target_info = static_cast(args.targetInfo), + .props = 0 + }; + mHandle = SDL_CreateGPUGraphicsPipeline(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUGraphicsPipeline(mDevice, mHandle); + mDevice = nullptr; + mHandle = nullptr; + } + } +}; + +struct GPUShaderCreateArgs +{ + std::span code; + std::string entrypoint = "main"; + GPUShaderFormat format; + GPUShaderStage stage; + Uint32 numSamplers = 0; + Uint32 numStorageTextures = 0; + Uint32 numStorageBuffers = 0; + Uint32 numUniformBuffers = 0; +}; + +class GPUShader : public BaseWithDevice +{ +public: + GPUShader() noexcept = default; + GPUShader(const GPUShader&) = delete; + GPUShader(GPUShader&& other) noexcept : BaseWithDevice(std::move(other)) {} + + GPUShader& operator=(const GPUShader&) = delete; + GPUShader& operator=(GPUShader&& other) noexcept + { + BaseWithDevice::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUShader& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUShaderCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUShader has already been created."); + const SDL_GPUShaderCreateInfo createInfo = + { + .code_size = args.code.size(), + .code = args.code.data(), + .entrypoint = args.entrypoint.c_str(), + .format = static_cast(args.format), + .stage = static_cast(args.stage), + .num_samplers = args.numSamplers, + .num_storage_textures = args.numStorageTextures, + .num_storage_buffers = args.numStorageBuffers, + .num_uniform_buffers = args.numUniformBuffers + }; + mHandle = SDL_CreateGPUShader(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUShader(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } +}; + +struct GPUBufferCreateArgs +{ + GPUBufferUsageFlags usage; + Uint32 size; +}; + +class GPUBuffer : public BaseWithDevice +{ +public: + GPUBuffer() noexcept = default; + GPUBuffer(const GPUBuffer&) = delete; + GPUBuffer(GPUBuffer&& other) noexcept : BaseWithDevice(std::move(other)) {} + + GPUBuffer& operator=(const GPUBuffer&) = delete; + GPUBuffer& operator=(GPUBuffer&& other) noexcept + { + BaseWithDevice::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUBuffer& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUBufferCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUBuffer has already been created."); + const SDL_GPUBufferCreateInfo createInfo = { + .usage = static_cast(args.usage), + .size = args.size, + .props = 0 + }; + mHandle = SDL_CreateGPUBuffer(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUBuffer(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } +}; + +struct GPUTransferBufferCreateArgs +{ + GPUTransferBufferUsage usage; + Uint32 size; +}; + +class GPUTransferBuffer : public BaseWithDevice +{ +public: + GPUTransferBuffer() noexcept = default; + GPUTransferBuffer(const GPUTransferBuffer&) = delete; + GPUTransferBuffer(GPUTransferBuffer&& other) noexcept : BaseWithDevice(std::move(other)) {} + + GPUTransferBuffer& operator=(const GPUTransferBuffer&) = delete; + GPUTransferBuffer& operator=(GPUTransferBuffer&& other) noexcept + { + BaseWithDevice::operator=(std::move(other)); + return *this; + } + auto operator<=>(const GPUTransferBuffer& other) const noexcept = default; + + void create(SDL_GPUDevice* device, const GPUTransferBufferCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "GPUTransferBuffer has already been created."); + const SDL_GPUTransferBufferCreateInfo createInfo = { + .usage = static_cast(args.usage), + .size = args.size, + .props = 0 + }; + mHandle = SDL_CreateGPUTransferBuffer(device, &createInfo); + if (mHandle == nullptr) + { + throw SDLError(); + } + mDevice = device; + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_ReleaseGPUTransferBuffer(mDevice, mHandle); + mHandle = nullptr; + mDevice = nullptr; + } + } + + void* map(bool cycle = false) noexcept + { + return SDL_MapGPUTransferBuffer(mDevice, mHandle, cycle); + } + + void unmap() noexcept + { + SDL_UnmapGPUTransferBuffer(mDevice, mHandle); + } +}; +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_GPU_HPP_INCLUDED) diff --git a/public/sdlpp/keyboard.hpp b/public/sdlpp/keyboard.hpp new file mode 100644 index 0000000..c3193d7 --- /dev/null +++ b/public/sdlpp/keyboard.hpp @@ -0,0 +1,20 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_KEYBOARD_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_KEYBOARD_HPP_INCLUDED 1 + +#include "./common.hpp" + +namespace sdlpp +{ +[[nodiscard]] +inline std::span getKeyboardState() noexcept +{ + int numkeys = 0; + const bool* states = SDL_GetKeyboardState(&numkeys); + return {states, static_cast(numkeys)}; +} +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_KEYBOARD_HPP_INCLUDED) diff --git a/public/sdlpp/vulkan.hpp b/public/sdlpp/vulkan.hpp new file mode 100644 index 0000000..d278a0a --- /dev/null +++ b/public/sdlpp/vulkan.hpp @@ -0,0 +1,74 @@ + +#pragma once + +#if !defined(SDLPP_PUBLIC_SDLPP_VULKAN_HPP_INCLUDED) +#define SDLPP_PUBLIC_SDLPP_VULKAN_HPP_INCLUDED 1 + +#include "./common.hpp" + +#include +#include "suna/vulkan/vkwrapper.hpp" // TODO: this shouldn't be in a generic sdlpp header + +namespace sdlpp +{ +struct VkSurfaceCreateArgs +{ + SDL_Window* window; + VkInstance instance; +}; + +class VkSurface : public Base, VkSurface> +{ +private: + VkInstance mInstance = nullptr; +public: + VkSurface() noexcept = default; + VkSurface(const VkSurface&) = delete; + VkSurface(VkSurface&& other) noexcept : Base(std::move(other)), mInstance(std::exchange(other.mInstance, nullptr)) {} + + VkSurface& operator=(const VkSurface&) = delete; + VkSurface& operator=(VkSurface&& other) noexcept + { + Base::operator=(std::move(other)); + mInstance = std::exchange(other.mInstance, nullptr); + return *this; + } + auto operator<=>(const VkSurface& other) const noexcept = default; + + operator vk::SurfaceKHR() const noexcept { return mHandle; } + + void create(const VkSurfaceCreateArgs& args) + { + MIJIN_ASSERT(mHandle == nullptr, "VkSurface has already been created."); + if (!SDL_Vulkan_CreateSurface(args.window, args.instance, nullptr, &mHandle)) + { + throw SDLError(); + } + mInstance = args.instance; + } + + void destroy() + { + if (mHandle != nullptr) + { + SDL_Vulkan_DestroySurface(mInstance, mHandle, nullptr); + mHandle = nullptr; + mInstance = nullptr; + } + } +}; + +[[nodiscard]] +inline std::span getVulkanInstanceExtensions() +{ + Uint32 count = 0; + const char* const* extensions = SDL_Vulkan_GetInstanceExtensions(&count); + if (extensions == nullptr) + { + throw SDLError(); + } + return {extensions, count}; +} +} // namespace suna + +#endif // !defined(SDLPP_PUBLIC_SDLPP_VULKAN_HPP_INCLUDED) diff --git a/public/sdlpp/window.hpp b/public/sdlpp/window.hpp new file mode 100644 index 0000000..4723e6d --- /dev/null +++ b/public/sdlpp/window.hpp @@ -0,0 +1,113 @@ + +#pragma once + +#if !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_WINDOW_HPP_INCLUDED) +#define SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_WINDOW_HPP_INCLUDED 1 + +#include "./common.hpp" + +namespace sdlpp +{ +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 borderless : 1; // 0x0000000000000010 /**< no window decoration */ + bool resizable : 1; // 0x0000000000000020 /**< window can be resized */ + bool minimized : 1; // 0x0000000000000040 /**< window is minimized */ + bool maximized : 1; // 0x0000000000000080 /**< window is maximized */ + bool mouse_grabbed : 1; // 0x0000000000000100 /**< window has grabbed mouse input */ + bool input_focus : 1; // 0x0000000000000200 /**< window has input focus */ + bool mouse_focus : 1; // 0x0000000000000400 /**< window has mouse focus */ + 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_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 */ + bool tooltip : 1; // 0x0000000000040000 /**< window should be treated as a tooltip and does not get mouse or keyboard focus, requires a parent window */ + bool popup_menu : 1; // 0x0000000000080000 /**< window should be treated as a popup menu, requires a parent window */ + bool keyboard_grabbed : 1; // 0x0000000000100000 /**< window has grabbed keyboard input */ + bool unused0 : 1; // 0x0000000000200000 + bool unused1 : 1; // 0x0000000000400000 + bool unused2 : 1; // 0x0000000000800000 + bool unused3 : 1; // 0x0000000001000000 + bool unused4 : 1; // 0x0000000002000000 + bool unused5 : 1; // 0x0000000004000000 + bool unused6 : 1; // 0x0000000008000000 + bool vulkan : 1; // 0x0000000010000000 /**< window usable for Vulkan surface */ + bool metal : 1; // 0x0000000020000000 /**< window usable for Metal view */ + bool transparent : 1; // 0x0000000040000000 /**< window with transparent buffer */ + bool notFocusable : 1; // 0x0000000080000000 /**< window should not be focusable */ + + constexpr operator SDL_WindowFlags () const noexcept + { + return std::bit_cast(*this); + } +}; + +struct WindowCreateArgs +{ + const char* title = "Window"; + int width = 1280; + int height = 720; + WindowFlags flags = {}; +}; + +class Window : public Base +{ +public: + Window() noexcept = default; + Window(const Window&) = delete; + Window(Window&& other) noexcept : Base(std::move(other)) {} + + Window& operator=(const Window&) = delete; + Window& operator=(Window&& other) noexcept + { + Base::operator=(std::move(other)); + return *this; + } + auto operator<=>(const Window& other) const noexcept = default; + + void create(const WindowCreateArgs& args = {}) + { + MIJIN_ASSERT(mHandle == nullptr, "Window has already been created."); + mHandle = SDL_CreateWindow(args.title, args.width, args.height, args.flags); + if (mHandle == nullptr) + { + throw SDLError(); + } + } + + void destroy() noexcept + { + if (mHandle != nullptr) + { + SDL_DestroyWindow(mHandle); + mHandle = nullptr; + } + } + + bool setRelativeMouseMode(bool enabled) + { + return SDL_SetWindowRelativeMouseMode(mHandle, enabled); + } + + [[nodiscard]] + std::pair getSizeInPixels() const + { + int width = 0; + int height = 0; + if (!SDL_GetWindowSizeInPixels(mHandle, &width, &height)) + { + throw SDLError(); + } + return {width, height}; + } +}; +} // namespace sdlpp + +#endif // !defined(SDL_GPU_TEST_PRIVATE_SDL_GPU_TEST_SDLPP_WINDOW_HPP_INCLUDED)