4---------------------5 /| /| / | +y / | / | | / | / | | / | / | | / | 0-----|-------|-------1 | | | | | | | | | +z | | | | | / | | | 6-------|/------|-----7 -----|----/--------*-------|----/------+x | / | | / | / | | / | / | | / |/ | |/ 2---------------------3 | | | |
0------------------1 1 | / /| | Triangle 1 / / | | / / | | / / | | / / | | / / | | / / | | / / Triangle 2 | | / / | 2 2-----------------3
Unlike with direct vector math, with matrices, most math is going to be done either with 3x3 for 2D operations or 4x4 for 3D operations. We'll show this math in 3D and thus 4x4 matrices.
Before going over the code and formula, we should make sure we understand how Matrix Multiplication works.
The identity matrix is generally a "base" matrix. Multiplying against it will return the same matrix you started with. It's simply a series of 1s along it's diagonal.
Math:
Code:
float4x4 IdentityMatrix() { float4x4 toReturn; SDL_zero(toReturn); toReturn.data[0][0] = 1.0f; toReturn.data[1][1] = 1.0f; toReturn.data[2][2] = 1.0f; toReturn.data[3][3] = 1.0f; return toReturn; }
Math:
Code:
float4x4 TranslationMatrix(float4 aPosition) { float4x4 toReturn = IdentityMatrix(); toReturn.data[3][0] = aPosition.x; toReturn.data[3][1] = aPosition.y; toReturn.data[3][2] = aPosition.z; return toReturn; }
Math:
Code:
float4x4 ScaleMatrix(float4 aScale) { float4x4 toReturn = IdentityMatrix(); toReturn.data[0][0] = aScale.x; toReturn.data[1][1] = aScale.y; toReturn.data[2][2] = aScale.z; return toReturn; }
Math:
Code:
float4x4 RotationMatrixX(float aAngle) { float4x4 toReturn = IdentityMatrix(); toReturn.data[1][1] = SDL_cosf(aAngle); toReturn.data[1][2] = SDL_sinf(aAngle); toReturn.data[2][1] = -SDL_sinf(aAngle); toReturn.data[2][2] = SDL_cosf(aAngle); return toReturn; }
Math:
Code:
float4x4 RotationMatrixY(float aAngle) { float4x4 toReturn = IdentityMatrix(); toReturn.data[0][0] = SDL_cosf(aAngle); toReturn.data[0][2] = -SDL_sinf(aAngle); toReturn.data[2][0] = SDL_sinf(aAngle); toReturn.data[2][2] = SDL_cosf(aAngle); return toReturn; }
Math:
Code:
float4x4 RotationMatrixZ(float aAngle) { float4x4 toReturn = IdentityMatrix(); toReturn.data[0][0] = SDL_cosf(aAngle); toReturn.data[0][1] = SDL_sinf(aAngle); toReturn.data[1][0] = -SDL_sinf(aAngle); toReturn.data[1][1] = SDL_cosf(aAngle); return toReturn; }
It's time to build out some matrix math functionality. If you're already comfortable with these topics, and prefer to just use your own, or copy/paste the one from this sample, that is entirely valid.
Note: The implementations here are naive and intended for learning, rather than high performance work. I intend to teach you how to do things effectively and ideally in a fairly performant manner in this series, but I'm not counting CPU cycles or planning to drop down into SIMD.
#include <SDL3/SDL.h> #include <SDL3/SDL_stdinc.h> // This is for testing to ensure the code works in both C and C++, // this entire preprocessor block should just be the #include // in your own code. #ifndef __cplusplus #include <SDL3/SDL_main.h> #else namespace cpp_test { #endif ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // MATH ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef struct float2 { float x, y; } float2; typedef struct float3 { float x, y, z; } float3; typedef struct float4 { float x, y, z, w; } float4; typedef struct float4x4 { union { float4 columns[4]; float data[4][4]; }; } float4x4; typedef struct Transform { float4 mPosition; float4 mScale; float4 mRotation; } Transform; ////////////////////////////////////////////////////// // Downcasts float2 Float3_XY(float3 aValue) { float2 toReturn = { aValue.x, aValue.y }; return toReturn; } float2 Float4_XY(float4 aValue) { float2 toReturn = { aValue.x, aValue.y }; return toReturn; } float3 Float4_XYZ(float4 aValue) { float3 toReturn = { aValue.x, aValue.y, aValue.z }; return toReturn; } ////////////////////////////////////////////////////// // Subtraction float2 Float2_Subtract(float2 aLeft, float2 aRight) { float2 toReturn = { aLeft.x - aRight.x, aLeft.y - aRight.y }; return toReturn; } float3 Float3_Subtract(float3 aLeft, float3 aRight) { float3 toReturn = { aLeft.x - aRight.x, aLeft.y - aRight.y, aLeft.z - aRight.z }; return toReturn; } float4 Float4_Subtract(float4 aLeft, float4 aRight) { float4 toReturn = { aLeft.x - aRight.x, aLeft.y - aRight.y, aLeft.z - aRight.z, aLeft.w - aRight.w }; return toReturn; } ////////////////////////////////////////////////////// // Addition float2 Float2_Add(float2 aLeft, float2 aRight) { float2 toReturn = { aLeft.x + aRight.x, aLeft.y + aRight.y }; return toReturn; } float3 Float3_Add(float3 aLeft, float3 aRight) { float3 toReturn = { aLeft.x + aRight.x, aLeft.y + aRight.y, aLeft.z + aRight.z }; return toReturn; } float4 Float4_Add(float4 aLeft, float4 aRight) { float4 toReturn = { aLeft.x + aRight.x, aLeft.y + aRight.y, aLeft.z + aRight.z, aLeft.w + aRight.w }; return toReturn; } ////////////////////////////////////////////////////// // Multiplication float2 Float2_Multiply(float2 aLeft, float2 aRight) { float2 toReturn = { aLeft.x * aRight.x, aLeft.y * aRight.y }; return toReturn; } float3 Float3_Multiply(float3 aLeft, float3 aRight) { float3 toReturn = { aLeft.x * aRight.x, aLeft.y * aRight.y, aLeft.z * aRight.z }; return toReturn; } float4 Float4_Multiply(float4 aLeft, float4 aRight) { float4 toReturn = { aLeft.x * aRight.x, aLeft.y * aRight.y, aLeft.z * aRight.z, aLeft.w * aRight.w }; return toReturn; } ////////////////////////////////////////////////////// // Scalar Addition float2 Float2_Scalar_Add(float2 aLeft, float aRight) { float2 toReturn = { aLeft.x + aRight, aLeft.y + aRight }; return toReturn; } float3 Float3_Scalar_Add(float3 aLeft, float aRight) { float3 toReturn = { aLeft.x + aRight, aLeft.y + aRight, aLeft.z + aRight }; return toReturn; } float4 Float4_Scalar_Add(float4 aLeft, float aRight) { float4 toReturn = { aLeft.x + aRight, aLeft.y + aRight, aLeft.z + aRight, aLeft.w + aRight }; return toReturn; } ////////////////////////////////////////////////////// // Scalar Multiplication float2 Float2_Scalar_Multiply(float2 aLeft, float aRight) { float2 toReturn = { aLeft.x * aRight, aLeft.y * aRight }; return toReturn; } float3 Float3_Scalar_Multiply(float3 aLeft, float aRight) { float3 toReturn = { aLeft.x * aRight, aLeft.y * aRight, aLeft.z * aRight }; return toReturn; } float4 Float4_Scalar_Multiply(float4 aLeft, float aRight) { float4 toReturn = { aLeft.x * aRight, aLeft.y * aRight, aLeft.z * aRight, aLeft.w * aRight }; return toReturn; } ////////////////////////////////////////////////////// // Scalar Divison float2 Float2_Scalar_Division(float2 aLeft, float aRight) { float2 toReturn = { aLeft.x / aRight, aLeft.y / aRight }; return toReturn; } float3 Float3_Scalar_Division(float3 aLeft, float aRight) { float3 toReturn = { aLeft.x / aRight, aLeft.y / aRight, aLeft.z / aRight }; return toReturn; } float4 Float4_Scalar_Division(float4 aLeft, float aRight) { float4 toReturn = { aLeft.x / aRight, aLeft.y / aRight, aLeft.z / aRight, aLeft.w / aRight }; return toReturn; } ////////////////////////////////////////////////////// // Dot Product float Float2_Dot(float2 aLeft, float2 aRight) { return (aLeft.x * aRight.x) + (aLeft.y * aRight.y); } float Float3_Dot(float3 aLeft, float3 aRight) { return (aLeft.x * aRight.x) + (aLeft.y * aRight.y) + (aLeft.z * aRight.z); } float Float4_Dot(float4 aLeft, float4 aRight) { return (aLeft.x * aRight.x) + (aLeft.y * aRight.y) + (aLeft.z * aRight.z) + (aLeft.w * aRight.w); } ////////////////////////////////////////////////////// // Cross Product float3 Float3_Cross(float3 aLeft, float3 aRight) { float3 toReturn = { (aLeft.y * aRight.z) - (aLeft.z * aRight.y), (aLeft.z * aRight.x) - (aLeft.x * aRight.z), (aLeft.x * aRight.y) - (aLeft.y * aRight.x) }; return toReturn; } // Convience function that ignores the 4th component, assuming it was irrelevant. float3 Float4_Cross(float4 aLeft, float4 aRight) { float3 toReturn = { (aLeft.y * aRight.z) - (aLeft.z * aRight.y), (aLeft.z * aRight.x) - (aLeft.x * aRight.z), (aLeft.x * aRight.y) - (aLeft.y * aRight.x) }; return toReturn; } ////////////////////////////////////////////////////// // Magnitude float Float2_Magnitude(float2 aValue) { return SDL_sqrt(Float2_Dot(aValue, aValue)); } float Float3_Magnitude(float3 aValue) { return SDL_sqrt(Float3_Dot(aValue, aValue)); } float Float4_Magnitude(float4 aValue) { return SDL_sqrt(Float4_Dot(aValue, aValue)); } ////////////////////////////////////////////////////// // Normalization float2 Float2_Normalize(float2 aValue) { float magnitude = Float2_Magnitude(aValue); float2 toReturn = { aValue.x / magnitude, aValue.y / magnitude }; return toReturn; } float3 Float3_Normalize(float3 aValue) { float magnitude = Float3_Magnitude(aValue); float3 toReturn = { aValue.x / magnitude, aValue.y / magnitude, aValue.z / magnitude }; return toReturn; } float4 Float4_Normalize(float4 aValue) { float magnitude = Float4_Magnitude(aValue); float4 toReturn = { aValue.x / magnitude, aValue.y / magnitude, aValue.z / magnitude, aValue.w / magnitude }; return toReturn; } ////////////////////////////////////////////////////// // Matrix Operations float4 Float4x4_Float4_Multiply(const float4x4* aLeft, const float4 aRight) { float4 toReturn; toReturn.x = (aLeft->data[0][0] * aRight.x) + (aLeft->data[1][0] * aRight.y) + (aLeft->data[2][0] * aRight.z) + (aLeft->data[3][0] * aRight.w); toReturn.y = (aLeft->data[0][1] * aRight.x) + (aLeft->data[1][1] * aRight.y) + (aLeft->data[2][1] * aRight.z) + (aLeft->data[3][1] * aRight.w); toReturn.z = (aLeft->data[0][2] * aRight.x) + (aLeft->data[1][2] * aRight.y) + (aLeft->data[2][2] * aRight.z) + (aLeft->data[3][2] * aRight.w); toReturn.w = (aLeft->data[0][3] * aRight.x) + (aLeft->data[1][3] * aRight.y) + (aLeft->data[2][3] * aRight.z) + (aLeft->data[3][3] * aRight.w); return toReturn; } float4x4 Float4x4_Multiply(const float4x4* aLeft, const float4x4* aRight) { float4x4 toReturn; SDL_zero(toReturn); for (size_t i = 0; i < 4; ++i) { toReturn.data[i][0] = aLeft->data[0][0] * aRight->data[i][0] + aLeft->data[1][0] * aRight->data[i][1] + aLeft->data[2][0] * aRight->data[i][2] + aLeft->data[3][0] * aRight->data[i][3]; toReturn.data[i][1] = aLeft->data[0][1] * aRight->data[i][0] + aLeft->data[1][1] * aRight->data[i][1] + aLeft->data[2][1] * aRight->data[i][2] + aLeft->data[3][1] * aRight->data[i][3]; toReturn.data[i][2] = aLeft->data[0][2] * aRight->data[i][0] + aLeft->data[1][2] * aRight->data[i][1] + aLeft->data[2][2] * aRight->data[i][2] + aLeft->data[3][2] * aRight->data[i][3]; toReturn.data[i][3] = aLeft->data[0][3] * aRight->data[i][0] + aLeft->data[1][3] * aRight->data[i][1] + aLeft->data[2][3] * aRight->data[i][2] + aLeft->data[3][3] * aRight->data[i][3]; } return toReturn; } //////////////////////////////////////////////////////////// /// Core Matrices float4x4 IdentityMatrix() { float4x4 toReturn; SDL_zero(toReturn); toReturn.data[0][0] = 1.0f; toReturn.data[1][1] = 1.0f; toReturn.data[2][2] = 1.0f; toReturn.data[3][3] = 1.0f; return toReturn; } float4x4 TranslationMatrix(float4 aPosition) { float4x4 toReturn = IdentityMatrix(); toReturn.data[3][0] = aPosition.x; toReturn.data[3][1] = aPosition.y; toReturn.data[3][2] = aPosition.z; return toReturn; } float4x4 ScaleMatrix(float4 aScale) { float4x4 toReturn = IdentityMatrix(); toReturn.data[0][0] = aScale.x; toReturn.data[1][1] = aScale.y; toReturn.data[2][2] = aScale.z; return toReturn; } float4x4 RotationMatrixX(float aAngle) { float4x4 toReturn = IdentityMatrix(); toReturn.data[1][1] = SDL_cosf(aAngle); toReturn.data[1][2] = SDL_sinf(aAngle); toReturn.data[2][1] = -SDL_sinf(aAngle); toReturn.data[2][2] = SDL_cosf(aAngle); return toReturn; } float4x4 RotationMatrixY(float aAngle) { float4x4 toReturn = IdentityMatrix(); toReturn.data[0][0] = SDL_cosf(aAngle); toReturn.data[0][2] = -SDL_sinf(aAngle); toReturn.data[2][0] = SDL_sinf(aAngle); toReturn.data[2][2] = SDL_cosf(aAngle); return toReturn; } float4x4 RotationMatrixZ(float aAngle) { float4x4 toReturn = IdentityMatrix(); toReturn.data[0][0] = SDL_cosf(aAngle); toReturn.data[0][1] = SDL_sinf(aAngle); toReturn.data[1][0] = -SDL_sinf(aAngle); toReturn.data[1][1] = SDL_cosf(aAngle); return toReturn; } float4x4 RotationMatrix(float4 aPosition) { float4x4 xRotation = RotationMatrixX(aPosition.x); float4x4 yRotation = RotationMatrixY(aPosition.y); float4x4 zRotation = RotationMatrixZ(aPosition.z); float4x4 xyRotation = Float4x4_Multiply(&yRotation, &xRotation); return Float4x4_Multiply(&zRotation, &xyRotation); } float4x4 CreateModelMatrix(float4 aPosition, float4 aScale, float4 aRotation) { float4x4 translation = TranslationMatrix(aPosition); float4x4 rotation = RotationMatrix(aRotation); float4x4 scale = ScaleMatrix(aScale); float4x4 scale_rotation = Float4x4_Multiply(&rotation, &scale); return Float4x4_Multiply(&translation, &scale_rotation); } float4x4 CreateModelMatrixFromTransform(const Transform* aTransform) { return CreateModelMatrix(aTransform->mPosition, aTransform->mScale, aTransform->mRotation); } float4x4 OrthographicProjectionLHZO(float aLeft, float aRight, float aBottom, float aTop, float aNear, float aFar) { float4x4 toReturn; SDL_zero(toReturn); toReturn.data[0][0] = 2.0f / (aRight - aLeft); toReturn.data[1][1] = 2.0f / (aTop - aBottom); toReturn.data[2][2] = 1.0f / (aFar - aNear); toReturn.data[3][0] = -(aRight + aLeft) / (aRight - aLeft); toReturn.data[3][1] = -(aTop + aBottom) / (aTop - aBottom); toReturn.data[3][2] = -aNear / (aFar - aNear); toReturn.data[3][3] = 1.0f; return toReturn; } float4x4 PerspectiveProjectionLHZO(float aFovY, float aAspectRatio, float aNear, float aFar) { float4x4 toReturn; SDL_zero(toReturn); const float focalLength = 1.0f / SDL_tan(aFovY * .5f); const float k = aFar / (aFar - aNear); toReturn.data[0][0] = focalLength / aAspectRatio; toReturn.data[1][1] = focalLength; toReturn.data[2][2] = k; toReturn.data[2][3] = 1.0f; toReturn.data[3][2] = -aNear * k; return toReturn; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Shared GPU Code ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef struct GpuContext { SDL_Window* mWindow; SDL_GPUDevice* mDevice; SDL_PropertiesID mProperties; const char* mShaderEntryPoint; SDL_GPUShaderFormat mChosenBackendFormat; const char* mChosenBackendFormatExtension; float4x4 WorldToNDC; } GpuContext; GpuContext gContext; void CreateGpuContext(SDL_Window* aWindow) { SDL_zero(gContext); gContext.mWindow = aWindow; gContext.mDevice = SDL_CreateGPUDevice(SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL, true, NULL); SDL_assert(gContext.mDevice); SDL_assert(SDL_ClaimWindowForGPUDevice(gContext.mDevice, gContext.mWindow)); gContext.mProperties = SDL_CreateProperties(); SDL_assert(gContext.mProperties); SDL_GPUShaderFormat availableFormats = SDL_GetGPUShaderFormats(gContext.mDevice); gContext.mShaderEntryPoint = NULL; if (availableFormats & SDL_GPU_SHADERFORMAT_SPIRV) { gContext.mChosenBackendFormat = SDL_GPU_SHADERFORMAT_SPIRV; gContext.mShaderEntryPoint = "main"; gContext.mChosenBackendFormatExtension = "spv"; } else if (availableFormats & SDL_GPU_SHADERFORMAT_MSL) { gContext.mChosenBackendFormat = SDL_GPU_SHADERFORMAT_MSL; gContext.mShaderEntryPoint = "main0"; gContext.mChosenBackendFormatExtension = "msl"; } else if (availableFormats & SDL_GPU_SHADERFORMAT_DXIL) { gContext.mChosenBackendFormat = SDL_GPU_SHADERFORMAT_DXIL; gContext.mShaderEntryPoint = "main"; gContext.mChosenBackendFormatExtension = "dxil"; } } void DestroyGpuContext() { SDL_DestroyProperties(gContext.mProperties); SDL_DestroyGPUDevice(gContext.mDevice); SDL_DestroyWindow(gContext.mWindow); SDL_zero(gContext); } SDL_GPUShader* CreateShader( const char* aShaderFilename, SDL_GPUShaderStage aShaderStage, Uint32 aSamplerCount, Uint32 aUniformBufferCount, Uint32 aStorageBufferCount, Uint32 aStorageTextureCount, SDL_PropertiesID aProperties) { char shader_path[4096]; SDL_snprintf(shader_path, SDL_arraysize(shader_path), "Assets/Shaders/%s/%s.%s", TARGET_NAME, aShaderFilename, gContext.mChosenBackendFormatExtension); size_t fileSize = 0; void* fileData = SDL_LoadFile(shader_path, &fileSize); SDL_assert(fileData); SDL_PropertiesID properties = gContext.mProperties; if (aProperties != 0) { properties = aProperties; } SDL_assert(SDL_SetStringProperty(properties, SDL_PROP_GPU_SHADER_CREATE_NAME_STRING, aShaderFilename)); SDL_GPUShaderCreateInfo shaderCreateInfo; SDL_zero(shaderCreateInfo); shaderCreateInfo.entrypoint = gContext.mShaderEntryPoint; shaderCreateInfo.format = gContext.mChosenBackendFormat; shaderCreateInfo.code = (Uint8*)fileData; shaderCreateInfo.code_size = fileSize; shaderCreateInfo.stage = aShaderStage; shaderCreateInfo.num_samplers = aSamplerCount; shaderCreateInfo.num_uniform_buffers = aUniformBufferCount; shaderCreateInfo.num_storage_buffers = aStorageBufferCount; shaderCreateInfo.num_storage_textures = aStorageTextureCount; shaderCreateInfo.props = properties; SDL_GPUShader* shader = SDL_CreateGPUShader(gContext.mDevice, &shaderCreateInfo); SDL_free(fileData); SDL_assert(shader); return shader; } SDL_GPUBuffer* CreateGPUBuffer(Uint32 aSize, SDL_GPUBufferUsageFlags aUsage, const char* aName) { SDL_GPUBufferCreateInfo createInfo; SDL_SetStringProperty(gContext.mProperties, SDL_PROP_GPU_BUFFER_CREATE_NAME_STRING, aName); createInfo.props = gContext.mProperties; createInfo.size = aSize; createInfo.usage = aUsage; SDL_GPUBuffer* buffer = SDL_CreateGPUBuffer(gContext.mDevice, &createInfo); SDL_assert(buffer); return buffer; } SDL_GPUTransferBuffer* CreateTransferBuffer(Uint32 aSize, SDL_GPUTransferBufferUsage aUsage, const char* aName) { SDL_SetStringProperty(gContext.mProperties, SDL_PROP_GPU_TRANSFERBUFFER_CREATE_NAME_STRING, aName); SDL_GPUTransferBufferCreateInfo transferBufferCreateInfo; SDL_zero(transferBufferCreateInfo); transferBufferCreateInfo.props = gContext.mProperties; transferBufferCreateInfo.size = aSize; transferBufferCreateInfo.usage = aUsage; SDL_GPUTransferBuffer* transferBuffer = SDL_CreateGPUTransferBuffer(gContext.mDevice, &transferBufferCreateInfo); SDL_assert(transferBuffer); return transferBuffer; } SDL_GPUTexture* CreateTexture(Uint32 aWidth, Uint32 aHeight, Uint32 layers_or_depth, Uint32 levels, SDL_GPUTextureUsageFlags aUsage, SDL_GPUTextureFormat aFormat, const char* aName) { SDL_SetStringProperty(gContext.mProperties, SDL_PROP_GPU_TEXTURE_CREATE_NAME_STRING, aName); SDL_GPUTextureCreateInfo textureCreateInfo; SDL_zero(textureCreateInfo); textureCreateInfo.width = aWidth; textureCreateInfo.height = aHeight; textureCreateInfo.layer_count_or_depth = layers_or_depth; textureCreateInfo.num_levels = levels; textureCreateInfo.usage = aUsage; textureCreateInfo.format = aFormat; textureCreateInfo.props = gContext.mProperties; return SDL_CreateGPUTexture(gContext.mDevice, &textureCreateInfo); } SDL_GPUTexture* CreateAndUploadTexture(SDL_GPUCopyPass* aCopyPass, const char* aTextureName) { char stringBuffer[4096]; SDL_snprintf(stringBuffer, SDL_arraysize(stringBuffer), "Assets/Images/%s", aTextureName); SDL_Surface* surface = SDL_LoadSurface(stringBuffer); if (surface->format != SDL_PIXELFORMAT_RGBA32) { SDL_Surface* temp = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32); SDL_DestroySurface(surface); surface = temp; } Uint32 textureSize = surface->h * surface->pitch; SDL_snprintf(stringBuffer, SDL_arraysize(stringBuffer), "CreateAndUploadTexture Transfer Buffer for %s", aTextureName); SDL_GPUTransferBuffer* transferBuffer = CreateTransferBuffer(textureSize, SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD, stringBuffer); void* transferPtr = SDL_MapGPUTransferBuffer(gContext.mDevice, transferBuffer, false); memcpy(transferPtr, surface->pixels, textureSize); SDL_UnmapGPUTransferBuffer(gContext.mDevice, transferBuffer); SDL_GPUCommandBuffer* commandBuffer = NULL; SDL_GPUCopyPass* copyPass = aCopyPass; bool needsToSubmit = NULL == copyPass; if (needsToSubmit) { commandBuffer = SDL_AcquireGPUCommandBuffer(gContext.mDevice); copyPass = SDL_BeginGPUCopyPass(commandBuffer); } SDL_GPUTexture* texture = CreateTexture(surface->w, surface->h, 1, 1, SDL_GPU_TEXTUREUSAGE_SAMPLER, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, aTextureName); SDL_assert(texture); // Copy to GPU SDL_GPUTextureTransferInfo textureTransferInfo; SDL_zero(textureTransferInfo); textureTransferInfo.pixels_per_row = surface->w; textureTransferInfo.rows_per_layer = surface->h; textureTransferInfo.transfer_buffer = transferBuffer; SDL_GPUTextureRegion textureRegion; SDL_zero(textureRegion); textureRegion.texture = texture; textureRegion.w = surface->w; textureRegion.h = surface->h; textureRegion.d = 1; SDL_UploadToGPUTexture( copyPass, &textureTransferInfo, &textureRegion, false ); if (needsToSubmit) { SDL_EndGPUCopyPass(copyPass); SDL_SubmitGPUCommandBuffer(commandBuffer); } SDL_ReleaseGPUTransferBuffer(gContext.mDevice, transferBuffer); SDL_DestroySurface(surface); return texture; } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Technique Code ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// typedef struct ModelUniform { float2 mPosition; float2 mScale; } ModelUniform; typedef struct TechniqueContext { SDL_GPUGraphicsPipeline* mPipeline; SDL_GPUTexture* mTexture; SDL_GPUSampler* mSampler; ModelUniform mUniform; Transform mUniform; } TechniqueContext; TechniqueContext CreateTechniqueContext() { SDL_GPUColorTargetDescription colorTargetDescription; SDL_zero(colorTargetDescription); colorTargetDescription.format = SDL_GetGPUSwapchainTextureFormat(gContext.mDevice, gContext.mWindow); SDL_GPUGraphicsPipelineCreateInfo graphicsPipelineCreateInfo; SDL_zero(graphicsPipelineCreateInfo); graphicsPipelineCreateInfo.target_info.num_color_targets = 1; graphicsPipelineCreateInfo.target_info.color_target_descriptions = &colorTargetDescription; graphicsPipelineCreateInfo.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST; graphicsPipelineCreateInfo.rasterizer_state.front_face = SDL_GPU_FRONTFACE_CLOCKWISE; graphicsPipelineCreateInfo.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_BACK; graphicsPipelineCreateInfo.vertex_shader = CreateShader( "TransformedQuad.vert", "Cube.vert", SDL_GPU_SHADERSTAGE_VERTEX, 0, 2, 0, 0, SDL_PROPERTY_TYPE_INVALID ); SDL_assert(graphicsPipelineCreateInfo.vertex_shader); graphicsPipelineCreateInfo.fragment_shader = CreateShader( "TransformedQuad.frag", "Cube.frag", SDL_GPU_SHADERSTAGE_FRAGMENT, 1, 0, 0, 0, SDL_PROPERTY_TYPE_INVALID ); SDL_assert(graphicsPipelineCreateInfo.fragment_shader); SDL_assert(SDL_SetStringProperty(gContext.mProperties, SDL_PROP_GPU_GRAPHICSPIPELINE_CREATE_NAME_STRING, "TechniqueContext")); TechniqueContext context; SDL_zero(context); context.mPipeline = SDL_CreateGPUGraphicsPipeline(gContext.mDevice, &graphicsPipelineCreateInfo); SDL_assert(context.mPipeline); context.mUniform.mPosition.x = 128.f; context.mUniform.mPosition.y = 128.f; context.mUniform.mScale.x = 256.f; context.mUniform.mScale.y = 256.f; context.mUniform.mPosition.x = 0.f; context.mUniform.mPosition.y = -1.f; context.mUniform.mPosition.z = 5.f; context.mUniform.mPosition.w = 0.f; context.mUniform.mScale.x = 0.5f; context.mUniform.mScale.y = 0.5f; context.mUniform.mScale.z = 0.5f; context.mUniform.mScale.w = 0.5f; context.mUniform.mRotation.x = 0.f; context.mUniform.mRotation.y = 0.f; context.mUniform.mRotation.z = 0.f; context.mUniform.mRotation.w = 0.f; SDL_GPUSamplerCreateInfo samplerCreateInfo; SDL_zero(samplerCreateInfo); context.mSampler = SDL_CreateGPUSampler(gContext.mDevice, &samplerCreateInfo); SDL_assert(context.mSampler); context.mTexture = CreateAndUploadTexture(NULL, "sample.bmp"); SDL_ReleaseGPUShader(gContext.mDevice, graphicsPipelineCreateInfo.vertex_shader); SDL_ReleaseGPUShader(gContext.mDevice, graphicsPipelineCreateInfo.fragment_shader); return context; } void DrawTechniqueContext(TechniqueContext* aContext, SDL_GPUCommandBuffer* aCommandBuffer, SDL_GPURenderPass* aRenderPass) { SDL_BindGPUGraphicsPipeline(aRenderPass, aContext->mPipeline); SDL_PushGPUVertexUniformData(aCommandBuffer, 0, &aContext->mUniform, sizeof(aContext->mUniform)); float4x4 model = CreateModelMatrixFromTransform(&aContext->mUniform); SDL_PushGPUVertexUniformData(aCommandBuffer, 0, &model, sizeof(model)); SDL_PushGPUVertexUniformData(aCommandBuffer, 1, &gContext.WorldToNDC, sizeof(gContext.WorldToNDC)); { SDL_GPUTextureSamplerBinding textureBinding; SDL_zero(textureBinding); textureBinding.texture = aContext->mTexture; textureBinding.sampler = aContext->mSampler; SDL_BindGPUFragmentSamplers(aRenderPass, 0, &textureBinding, 1); } SDL_DrawGPUPrimitives(aRenderPass, 6, 1, 0, 0); SDL_DrawGPUPrimitives(aRenderPass, 6 /* 6 per face */ * 6 /* 6 sides of our cube */, 1, 0, 0); } void DestroyTechniqueContext(TechniqueContext* aContext) { SDL_ReleaseGPUSampler(gContext.mDevice, aContext->mSampler); SDL_ReleaseGPUTexture(gContext.mDevice, aContext->mTexture); SDL_ReleaseGPUGraphicsPipeline(gContext.mDevice, aContext->mPipeline); SDL_zero(*aContext); } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Main ///////////////////////////////////////////////////////////////////////////////////////////////////////////////// int main(int argc, char** argv) { (void)argc; (void)argv; SDL_assert(SDL_Init(SDL_INIT_VIDEO)); SDL_Window* window = SDL_CreateWindow(TARGET_NAME, 1280, 720, 0); SDL_assert(window); CreateGpuContext(window); TechniqueContext context = CreateTechniqueContext(); const float speed = 200.f; const float speed = 5.f; Uint64 last_frame_ticks_so_far = SDL_GetTicksNS(); int keys; const bool* key_map = SDL_GetKeyboardState(&keys); bool running = true; while (running) { Uint64 current_frame_ticks_so_far = SDL_GetTicksNS(); float dt = (current_frame_ticks_so_far - last_frame_ticks_so_far) / 1000000000.f; last_frame_ticks_so_far = current_frame_ticks_so_far; SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.common.type) { case SDL_EVENT_QUIT: running = false; break; case SDL_EVENT_QUIT: running = false; break; } } int w = 0, h = 0; SDL_GetWindowSizeInPixels(gContext.mWindow, &w, &h); gContext.WorldToNDC = OrthographicProjectionLHZO( 0, w, 0, h, 0.0f, 1.0f gContext.WorldToNDC = PerspectiveProjectionLHZO( 45.0f * SDL_PI_F / 180.0f, (float)w / (float)h, 20.0f, 60.0f ); if (key_map[SDL_SCANCODE_D]) context.mUniform.mPosition.x += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_A]) context.mUniform.mPosition.x -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_W]) context.mUniform.mPosition.y += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_S]) context.mUniform.mPosition.y -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_R]) context.mUniform.mScale.x += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_F]) context.mUniform.mScale.x -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_T]) context.mUniform.mScale.y += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_G]) context.mUniform.mScale.y -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_D]) context.mUniform.mPosition.x += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_A]) context.mUniform.mPosition.x -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_W]) context.mUniform.mPosition.y += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_S]) context.mUniform.mPosition.y -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_E]) context.mUniform.mPosition.z += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_Q]) context.mUniform.mPosition.z -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_R]) context.mUniform.mScale.x += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_F]) context.mUniform.mScale.x -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_T]) context.mUniform.mScale.y += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_G]) context.mUniform.mScale.y -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_INSERT]) context.mUniform.mRotation.x += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_DELETE]) context.mUniform.mRotation.x -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_HOME]) context.mUniform.mRotation.y += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_END]) context.mUniform.mRotation.y -= speed * dt * 1.0f; if (key_map[SDL_SCANCODE_PAGEUP]) context.mUniform.mRotation.y += speed * dt * 1.0f; if (key_map[SDL_SCANCODE_PAGEDOWN]) context.mUniform.mRotation.y -= speed * dt * 1.0f; SDL_GPUCommandBuffer* commandBuffer = SDL_AcquireGPUCommandBuffer(gContext.mDevice); if (!commandBuffer) { SDL_Log("AcquireGPUCommandBuffer failed: %s", SDL_GetError()); continue; } SDL_GPUTexture* swapchainTexture; if (!SDL_WaitAndAcquireGPUSwapchainTexture(commandBuffer, gContext.mWindow, &swapchainTexture, NULL, NULL)) { SDL_Log("WaitAndAcquireGPUSwapchainTexture failed: %s", SDL_GetError()); continue; } SDL_GPUColorTargetInfo colorTargetInfo; SDL_zero(colorTargetInfo); colorTargetInfo.texture = swapchainTexture; colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR; colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE; colorTargetInfo.clear_color.r = 0.2f; colorTargetInfo.clear_color.g = 0.2f; colorTargetInfo.clear_color.b = 0.85f; colorTargetInfo.clear_color.a = 1.0f; SDL_GPURenderPass* renderPass = SDL_BeginGPURenderPass( commandBuffer, &colorTargetInfo, 1, NULL ); DrawTechniqueContext(&context, commandBuffer, renderPass); SDL_EndGPURenderPass(renderPass); SDL_SubmitGPUCommandBuffer(commandBuffer); } DestroyTechniqueContext(&context); DestroyGpuContext(); SDL_Quit(); return 0; } #ifdef __cplusplus } // end cpp_test #endif
Download the source for this example here.