isle-portable/miniwin/src/d3drm/backends/sdl3gpu/renderer.cpp
Anonymous Maarten e4690a57b8
Some checks are pending
CI / clang-format (push) Waiting to run
CI / ${{ matrix.name }} (false, --toolchain /usr/local/vitasdk/share/vita.toolchain.cmake, false, false, Ninja, Vita, ubuntu-latest, true, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0, false, false, Visual Studio 17 2022, true, Xbox One, windows-latest, amd64, false, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake, false, devkitpro/devkitarm:latest, false, Ninja, true, Nintendo 3DS, ubuntu-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, -DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/Switch.cmake, false, devkitpro/devkita64:latest, false, Ninja, Nintendo Switch, true, ubuntu-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, emcmake, false, false, true, Ninja, Emscripten, ubuntu-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (false, false, false, Ninja, true, MSVC (arm64), windows-latest, amd64_arm64, false) (push) Waiting to run
CI / ${{ matrix.name }} (false, false, true, Ninja, true, MSVC (x86), windows-latest, amd64_x86, false) (push) Waiting to run
CI / ${{ matrix.name }} (false, true, false, Ninja, true, MSVC (x64), windows-latest, amd64, false) (push) Waiting to run
CI / ${{ matrix.name }} (false, true, true, false, Ninja, true, MSVC (x64 Debug), windows-latest, amd64, false) (push) Waiting to run
CI / ${{ matrix.name }} (true, false, -DCMAKE_SYSTEM_NAME=iOS, false, false, Xcode, true, iOS, macos-15, true) (push) Waiting to run
CI / ${{ matrix.name }} (true, false, false, Ninja, true, mingw-w64-i686, mingw32, msys2 mingw32, windows-latest, msys2 {0}, true) (push) Waiting to run
CI / ${{ matrix.name }} (true, false, false, false, Ninja, Android, ubuntu-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (true, false, true, false, Ninja, macOS, macos-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (true, true, false, Ninja, true, mingw-w64-x86_64, mingw64, msys2 mingw64, windows-latest, msys2 {0}, true) (push) Waiting to run
CI / ${{ matrix.name }} (true, true, true, false, Ninja, true, Linux (Debug), ubuntu-latest, true) (push) Waiting to run
CI / ${{ matrix.name }} (true, true, true, false, Ninja, true, Linux, ubuntu-latest, true) (push) Waiting to run
CI / Flatpak (${{ matrix.arch }}) (aarch64, ubuntu-22.04-arm) (push) Waiting to run
CI / Flatpak (${{ matrix.arch }}) (x86_64, ubuntu-latest) (push) Waiting to run
CI / C++ (push) Waiting to run
CI / Release (push) Blocked by required conditions
Docker / Publish web port (push) Waiting to run
Plug leaks until Information Center, detected by the LeakSanitizer (#764)
2026-01-04 03:35:42 +01:00

1023 lines
32 KiB
C++

#include "ShaderIndex.h"
#include "d3drmrenderer.h"
#include "d3drmrenderer_sdl3gpu.h"
#include "ddraw_impl.h"
#include "mathutils.h"
#include "meshutils.h"
#include "miniwin.h"
#include <SDL3/SDL.h>
#include <cassert>
#include <cmath>
#include <cstddef>
struct ScopedSurface {
SDL_Surface* ptr = nullptr;
~ScopedSurface()
{
if (ptr) {
SDL_DestroySurface(ptr);
}
}
void release() { ptr = nullptr; }
};
struct ScopedTexture {
SDL_GPUDevice* dev;
SDL_GPUTexture* ptr = nullptr;
~ScopedTexture()
{
if (ptr) {
SDL_ReleaseGPUTexture(dev, ptr);
}
}
void release() { ptr = nullptr; }
};
struct ScopedTransferBuffer {
SDL_GPUDevice* dev;
SDL_GPUTransferBuffer* ptr = nullptr;
~ScopedTransferBuffer()
{
if (ptr) {
SDL_ReleaseGPUTransferBuffer(dev, ptr);
}
}
void release() { ptr = nullptr; }
};
struct ScopedDevice {
SDL_GPUDevice* ptr = nullptr;
~ScopedDevice()
{
if (ptr) {
SDL_DestroyGPUDevice(ptr);
}
}
void release() { ptr = nullptr; }
};
struct ScopedPipeline {
SDL_GPUDevice* dev;
SDL_GPUGraphicsPipeline* ptr = nullptr;
~ScopedPipeline()
{
if (ptr) {
SDL_ReleaseGPUGraphicsPipeline(dev, ptr);
}
}
void release() { ptr = nullptr; }
};
struct ScopedSampler {
SDL_GPUDevice* dev;
SDL_GPUSampler* ptr = nullptr;
~ScopedSampler()
{
if (ptr) {
SDL_ReleaseGPUSampler(dev, ptr);
}
}
void release() { ptr = nullptr; }
};
struct ScopedShader {
SDL_GPUDevice* dev;
SDL_GPUShader* ptr = nullptr;
~ScopedShader()
{
if (ptr) {
SDL_ReleaseGPUShader(dev, ptr);
}
}
void release() { ptr = nullptr; }
};
static SDL_GPUGraphicsPipeline* InitializeGraphicsPipeline(
SDL_GPUDevice* device,
SDL_Window* window,
bool depthTest,
bool depthWrite
)
{
const SDL_GPUShaderCreateInfo* vertexCreateInfo =
GetVertexShaderCode(VertexShaderId::PositionColor, SDL_GetGPUShaderFormats(device));
if (!vertexCreateInfo) {
return nullptr;
}
ScopedShader vertexShader{device, SDL_CreateGPUShader(device, vertexCreateInfo)};
if (!vertexShader.ptr) {
return nullptr;
}
const SDL_GPUShaderCreateInfo* fragmentCreateInfo =
GetFragmentShaderCode(FragmentShaderId::SolidColor, SDL_GetGPUShaderFormats(device));
if (!fragmentCreateInfo) {
return nullptr;
}
ScopedShader fragmentShader{device, SDL_CreateGPUShader(device, fragmentCreateInfo)};
if (!fragmentShader.ptr) {
return nullptr;
}
SDL_GPUVertexBufferDescription vertexBufferDescs[1] = {};
vertexBufferDescs[0].slot = 0;
vertexBufferDescs[0].pitch = sizeof(D3DRMVERTEX);
vertexBufferDescs[0].input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
SDL_GPUVertexAttribute vertexAttrs[3] = {};
vertexAttrs[0].location = 0;
vertexAttrs[0].buffer_slot = 0;
vertexAttrs[0].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
vertexAttrs[0].offset = offsetof(D3DRMVERTEX, position);
vertexAttrs[1].location = 1;
vertexAttrs[1].buffer_slot = 0;
vertexAttrs[1].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3;
vertexAttrs[1].offset = offsetof(D3DRMVERTEX, normal);
vertexAttrs[2].location = 2;
vertexAttrs[2].buffer_slot = 0;
vertexAttrs[2].format = SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2;
vertexAttrs[2].offset = offsetof(D3DRMVERTEX, texCoord);
SDL_GPUVertexInputState vertexInputState = {};
vertexInputState.vertex_buffer_descriptions = vertexBufferDescs;
vertexInputState.num_vertex_buffers = SDL_arraysize(vertexBufferDescs);
vertexInputState.vertex_attributes = vertexAttrs;
vertexInputState.num_vertex_attributes = SDL_arraysize(vertexAttrs);
SDL_GPUColorTargetDescription colorTargets = {};
colorTargets.format = SDL_GetGPUSwapchainTextureFormat(device, window);
if (depthTest && depthWrite) {
colorTargets.blend_state.enable_blend = false;
}
else {
colorTargets.blend_state.enable_blend = true;
colorTargets.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
colorTargets.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
colorTargets.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
colorTargets.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
colorTargets.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ZERO;
colorTargets.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
colorTargets.blend_state.enable_color_write_mask = false;
}
SDL_GPURasterizerState rasterizerState = {};
rasterizerState.fill_mode = SDL_GPU_FILLMODE_FILL;
rasterizerState.cull_mode = SDL_GPU_CULLMODE_BACK;
rasterizerState.front_face = SDL_GPU_FRONTFACE_CLOCKWISE;
rasterizerState.enable_depth_clip = true;
SDL_GPUGraphicsPipelineCreateInfo pipelineCreateInfo = {};
pipelineCreateInfo.vertex_shader = vertexShader.ptr;
pipelineCreateInfo.fragment_shader = fragmentShader.ptr;
pipelineCreateInfo.vertex_input_state = vertexInputState;
pipelineCreateInfo.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
pipelineCreateInfo.rasterizer_state = rasterizerState;
pipelineCreateInfo.depth_stencil_state.compare_op = SDL_GPU_COMPAREOP_GREATER;
pipelineCreateInfo.depth_stencil_state.write_mask = 0xff;
pipelineCreateInfo.depth_stencil_state.enable_depth_test = depthTest;
pipelineCreateInfo.depth_stencil_state.enable_depth_write = depthWrite;
pipelineCreateInfo.depth_stencil_state.enable_stencil_test = false;
pipelineCreateInfo.target_info.color_target_descriptions = &colorTargets;
pipelineCreateInfo.target_info.num_color_targets = 1;
pipelineCreateInfo.target_info.depth_stencil_format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
pipelineCreateInfo.target_info.has_depth_stencil_target = true;
return SDL_CreateGPUGraphicsPipeline(device, &pipelineCreateInfo);
}
Direct3DRMRenderer* Direct3DRMSDL3GPURenderer::Create(DWORD width, DWORD height)
{
ScopedDevice device{SDL_CreateGPUDevice(
SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXBC | SDL_GPU_SHADERFORMAT_DXIL | SDL_GPU_SHADERFORMAT_MSL,
#ifdef DEBUG
true,
#else
false,
#endif
nullptr
)};
if (!device.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUDevice failed (%s)", SDL_GetError());
return nullptr;
}
if (!DDWindow) {
SDL_Log("No window handler");
return nullptr;
}
if (!SDL_ClaimWindowForGPUDevice(device.ptr, DDWindow)) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_ClaimWindowForGPUDevice: %s", SDL_GetError());
return nullptr;
}
ScopedPipeline opaquePipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, true, true)};
if (!opaquePipeline.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for opaquePipeline");
return nullptr;
}
ScopedPipeline transparentPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, true, false)};
if (!transparentPipeline.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for transparentPipeline");
return nullptr;
}
ScopedPipeline uiPipeline{device.ptr, InitializeGraphicsPipeline(device.ptr, DDWindow, false, false)};
if (!uiPipeline.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "InitializeGraphicsPipeline for uiPipeline");
return nullptr;
}
// Setup texture CPU-to-GPU transfer
SDL_GPUTransferBufferCreateInfo uploadBufferInfo = {};
uploadBufferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
int uploadBufferSize = 640 * 480 * 4; // Largest known game texture
uploadBufferInfo.size = uploadBufferSize;
ScopedTransferBuffer uploadBuffer{device.ptr, SDL_CreateGPUTransferBuffer(device.ptr, &uploadBufferInfo)};
if (!uploadBuffer.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUTransferBuffer filed for upload buffer (%s)", SDL_GetError());
return nullptr;
}
SDL_GPUSamplerCreateInfo samplerInfo = {};
samplerInfo.min_filter = SDL_GPU_FILTER_LINEAR;
samplerInfo.mag_filter = SDL_GPU_FILTER_LINEAR;
samplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
samplerInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
samplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
samplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
ScopedSampler sampler{device.ptr, SDL_CreateGPUSampler(device.ptr, &samplerInfo)};
if (!sampler.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create sampler: %s", SDL_GetError());
return nullptr;
}
SDL_GPUSamplerCreateInfo uiSamplerInfo = {};
uiSamplerInfo.min_filter = SDL_GPU_FILTER_LINEAR;
uiSamplerInfo.mag_filter = SDL_GPU_FILTER_NEAREST;
uiSamplerInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
uiSamplerInfo.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
uiSamplerInfo.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
uiSamplerInfo.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
ScopedSampler uiSampler{device.ptr, SDL_CreateGPUSampler(device.ptr, &uiSamplerInfo)};
if (!uiSampler.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create sampler: %s", SDL_GetError());
return nullptr;
}
auto renderer = new Direct3DRMSDL3GPURenderer(
width,
height,
device.ptr,
opaquePipeline.ptr,
transparentPipeline.ptr,
uiPipeline.ptr,
sampler.ptr,
uiSampler.ptr,
uploadBuffer.ptr,
uploadBufferSize
);
// Release resources so they don't get cleaned up
device.release();
opaquePipeline.release();
transparentPipeline.release();
uiPipeline.release();
sampler.release();
uiSampler.release();
uploadBuffer.release();
return renderer;
}
Direct3DRMSDL3GPURenderer::Direct3DRMSDL3GPURenderer(
DWORD width,
DWORD height,
SDL_GPUDevice* device,
SDL_GPUGraphicsPipeline* opaquePipeline,
SDL_GPUGraphicsPipeline* transparentPipeline,
SDL_GPUGraphicsPipeline* uiPipeline,
SDL_GPUSampler* sampler,
SDL_GPUSampler* uiSampler,
SDL_GPUTransferBuffer* uploadBuffer,
int uploadBufferSize
)
: m_device(device), m_opaquePipeline(opaquePipeline), m_transparentPipeline(transparentPipeline),
m_uiPipeline(uiPipeline), m_sampler(sampler), m_uiSampler(uiSampler), m_uploadBuffer(uploadBuffer),
m_uploadBufferSize(uploadBufferSize)
{
m_width = width;
m_height = height;
m_virtualWidth = width;
m_virtualHeight = height;
SDL_Surface* dummySurface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32);
if (!dummySurface) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create surface: %s", SDL_GetError());
return;
}
if (!SDL_LockSurface(dummySurface)) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to lock surface: %s", SDL_GetError());
SDL_DestroySurface(dummySurface);
return;
}
((Uint32*) dummySurface->pixels)[0] = 0xFFFFFFFF;
SDL_UnlockSurface(dummySurface);
m_dummyTexture = CreateTextureFromSurface(dummySurface);
if (!m_dummyTexture) {
SDL_DestroySurface(dummySurface);
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create surface: %s", SDL_GetError());
return;
}
SDL_DestroySurface(dummySurface);
ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f};
Resize(m_width, m_height, viewportTransform);
m_uiMesh.vertices = {
{{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}},
{{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}},
{{1.0f, 1.0f, 0.0f}, {0, 0, -1}, {1.0f, 1.0f}},
{{0.0f, 1.0f, 0.0f}, {0, 0, -1}, {0.0f, 1.0f}}
};
m_uiMesh.indices = {0, 1, 2, 0, 2, 3};
m_uiMeshCache = UploadMesh(m_uiMesh);
}
Direct3DRMSDL3GPURenderer::~Direct3DRMSDL3GPURenderer()
{
assert(m_refCount == 0);
SDL_ReleaseGPUBuffer(m_device, m_uiMeshCache.vertexBuffer);
SDL_ReleaseGPUBuffer(m_device, m_uiMeshCache.indexBuffer);
SDL_ReleaseWindowFromGPUDevice(m_device, DDWindow);
if (m_downloadBuffer) {
SDL_ReleaseGPUTransferBuffer(m_device, m_downloadBuffer);
}
if (m_uploadBuffer) {
SDL_ReleaseGPUTransferBuffer(m_device, m_uploadBuffer);
}
SDL_ReleaseGPUSampler(m_device, m_sampler);
SDL_ReleaseGPUSampler(m_device, m_uiSampler);
if (m_dummyTexture) {
SDL_ReleaseGPUTexture(m_device, m_dummyTexture);
}
if (m_depthTexture) {
SDL_ReleaseGPUTexture(m_device, m_depthTexture);
}
if (m_transferTexture) {
SDL_ReleaseGPUTexture(m_device, m_transferTexture);
}
SDL_ReleaseGPUGraphicsPipeline(m_device, m_opaquePipeline);
SDL_ReleaseGPUGraphicsPipeline(m_device, m_transparentPipeline);
SDL_ReleaseGPUGraphicsPipeline(m_device, m_uiPipeline);
if (m_uploadFence) {
SDL_ReleaseGPUFence(m_device, m_uploadFence);
}
SDL_DestroyGPUDevice(m_device);
}
void Direct3DRMSDL3GPURenderer::PushLights(const SceneLight* lights, size_t count)
{
if (count > 3) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Unsupported number of lights (%d)", static_cast<int>(count));
count = 3;
}
int lightCount = std::min(static_cast<int>(count), 3);
memcpy(&m_fragmentShadingData.lights, lights, sizeof(SceneLight) * lightCount);
m_fragmentShadingData.lightCount = lightCount;
}
void Direct3DRMSDL3GPURenderer::SetFrustumPlanes(const Plane* frustumPlanes)
{
}
void Direct3DRMSDL3GPURenderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back)
{
m_front = front;
m_back = back;
memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D));
}
void Direct3DRMSDL3GPURenderer::WaitForPendingUpload()
{
if (!m_uploadFence) {
return;
}
bool success = SDL_WaitForGPUFences(m_device, true, &m_uploadFence, 1);
SDL_ReleaseGPUFence(m_device, m_uploadFence);
m_uploadFence = nullptr;
if (!success) {
return;
}
}
struct SDLTextureDestroyContext {
Direct3DRMSDL3GPURenderer* renderer;
Uint32 id;
};
void Direct3DRMSDL3GPURenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture)
{
auto* ctx = new SDLTextureDestroyContext{this, id};
texture->AddDestroyCallback(
[](IDirect3DRMObject*, void* arg) {
auto* ctx = static_cast<SDLTextureDestroyContext*>(arg);
auto& cache = ctx->renderer->m_textures[ctx->id];
if (cache.gpuTexture) {
SDL_ReleaseGPUTexture(ctx->renderer->m_device, cache.gpuTexture);
cache.gpuTexture = nullptr;
cache.texture = nullptr;
}
delete ctx;
},
ctx
);
}
SDL_GPUTexture* Direct3DRMSDL3GPURenderer::CreateTextureFromSurface(SDL_Surface* surface)
{
ScopedSurface surf{SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32)};
if (!surf.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_ConvertSurface (%s)", SDL_GetError());
return nullptr;
}
const Uint32 dataSize = surf.ptr->pitch * surf.ptr->h;
SDL_GPUTextureCreateInfo textureInfo = {};
textureInfo.type = SDL_GPU_TEXTURETYPE_2D;
textureInfo.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
textureInfo.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
textureInfo.width = surf.ptr->w;
textureInfo.height = surf.ptr->h;
textureInfo.layer_count_or_depth = 1;
textureInfo.num_levels = 1;
ScopedTexture texture{m_device, SDL_CreateGPUTexture(m_device, &textureInfo)};
if (!texture.ptr) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUTexture (%s)", SDL_GetError());
return nullptr;
}
SDL_GPUTransferBuffer* transferBuffer = GetUploadBuffer(dataSize);
void* transferData = SDL_MapGPUTransferBuffer(m_device, transferBuffer, false);
if (!transferData) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_MapGPUTransferBuffer (%s)", SDL_GetError());
return nullptr;
}
memcpy(transferData, surf.ptr->pixels, dataSize);
SDL_UnmapGPUTransferBuffer(m_device, transferBuffer);
SDL_GPUTextureTransferInfo transferRegionInfo = {};
transferRegionInfo.transfer_buffer = transferBuffer;
SDL_GPUTextureRegion textureRegion = {};
textureRegion.texture = texture.ptr;
textureRegion.w = surf.ptr->w;
textureRegion.h = surf.ptr->h;
textureRegion.d = 1;
SDL_GPUCommandBuffer* cmdbuf = SDL_AcquireGPUCommandBuffer(m_device);
if (!cmdbuf) {
SDL_LogError(
LOG_CATEGORY_MINIWIN,
"SDL_AcquireGPUCommandBuffer in CreateTextureFromSurface failed (%s)",
SDL_GetError()
);
return nullptr;
}
SDL_GPUCopyPass* pass = SDL_BeginGPUCopyPass(cmdbuf);
SDL_UploadToGPUTexture(pass, &transferRegionInfo, &textureRegion, false);
SDL_EndGPUCopyPass(pass);
m_uploadFence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmdbuf);
auto texptr = texture.ptr;
// Release texture ownership so caller can manage it safely
texture.release();
return texptr;
}
Uint32 Direct3DRMSDL3GPURenderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
SDL_Surface* surf = surface->m_surface;
for (Uint32 i = 0; i < m_textures.size(); ++i) {
auto& tex = m_textures[i];
if (tex.texture == texture) {
if (tex.version != texture->m_version) {
SDL_ReleaseGPUTexture(m_device, tex.gpuTexture);
tex.gpuTexture = CreateTextureFromSurface(surf);
if (!tex.gpuTexture) {
return NO_TEXTURE_ID;
}
tex.version = texture->m_version;
}
return i;
}
}
SDL_GPUTexture* newTex = CreateTextureFromSurface(surf);
if (!newTex) {
return NO_TEXTURE_ID;
}
for (Uint32 i = 0; i < m_textures.size(); ++i) {
auto& tex = m_textures[i];
if (!tex.texture) {
tex = {texture, texture->m_version, newTex};
AddTextureDestroyCallback(i, texture);
return i;
}
}
m_textures.push_back({texture, texture->m_version, newTex});
AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture);
return (Uint32) (m_textures.size() - 1);
}
SDL3MeshCache Direct3DRMSDL3GPURenderer::UploadMesh(const MeshGroup& meshGroup)
{
std::vector<D3DRMVERTEX> finalVertices;
std::vector<Uint16> finalIndices;
if (meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT) {
std::vector<uint16_t> newIndices;
FlattenSurfaces(
meshGroup.vertices.data(),
meshGroup.vertices.size(),
meshGroup.indices.data(),
meshGroup.indices.size(),
true,
finalVertices,
newIndices
);
finalIndices.assign(newIndices.begin(), newIndices.end());
}
else {
finalVertices = meshGroup.vertices;
finalIndices.assign(meshGroup.indices.begin(), meshGroup.indices.end());
}
SDL_GPUBufferCreateInfo vertexBufferCreateInfo = {};
vertexBufferCreateInfo.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
vertexBufferCreateInfo.size = sizeof(D3DRMVERTEX) * finalVertices.size();
SDL_GPUBuffer* vertexBuffer = SDL_CreateGPUBuffer(m_device, &vertexBufferCreateInfo);
if (!vertexBuffer) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUBuffer failed (%s)", SDL_GetError());
return {};
}
SDL_GPUBufferCreateInfo indexBufferCreateInfo = {};
indexBufferCreateInfo.usage = SDL_GPU_BUFFERUSAGE_INDEX;
indexBufferCreateInfo.size = sizeof(Uint16) * finalIndices.size();
SDL_GPUBuffer* indexBuffer = SDL_CreateGPUBuffer(m_device, &indexBufferCreateInfo);
if (!indexBuffer) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Failed to create index buffer (%s)", SDL_GetError());
return {};
}
size_t verticesSize = sizeof(D3DRMVERTEX) * finalVertices.size();
size_t indicesSize = sizeof(Uint16) * finalIndices.size();
SDL_GPUTransferBuffer* uploadBuffer = GetUploadBuffer(verticesSize + indicesSize);
if (!uploadBuffer) {
return {};
}
auto* transferData = static_cast<Uint8*>(SDL_MapGPUTransferBuffer(m_device, uploadBuffer, false));
if (!transferData) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Transfer buffer mapping failed (%s)", SDL_GetError());
return {};
}
memcpy(transferData, finalVertices.data(), verticesSize);
memcpy(transferData + verticesSize, finalIndices.data(), indicesSize);
SDL_UnmapGPUTransferBuffer(m_device, uploadBuffer);
SDL_GPUTransferBufferLocation transferLocation = {uploadBuffer};
// Upload vertex + index data to GPU
SDL_GPUCommandBuffer* cmdbuf = SDL_AcquireGPUCommandBuffer(m_device);
if (!cmdbuf) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_AcquireGPUCommandBuffer failed (%s)", SDL_GetError());
return {};
}
SDL_GPUCopyPass* copyPass = SDL_BeginGPUCopyPass(cmdbuf);
SDL_GPUBufferRegion vertexRegion = {};
vertexRegion.buffer = vertexBuffer;
vertexRegion.size = verticesSize;
SDL_UploadToGPUBuffer(copyPass, &transferLocation, &vertexRegion, false);
SDL_GPUTransferBufferLocation indexTransferLocation = {};
indexTransferLocation.transfer_buffer = uploadBuffer;
indexTransferLocation.offset = verticesSize;
SDL_GPUBufferRegion indexRegion = {};
indexRegion.buffer = indexBuffer;
indexRegion.size = indicesSize;
SDL_UploadToGPUBuffer(copyPass, &indexTransferLocation, &indexRegion, false);
SDL_EndGPUCopyPass(copyPass);
m_uploadFence = SDL_SubmitGPUCommandBufferAndAcquireFence(cmdbuf);
return {&meshGroup, meshGroup.version, vertexBuffer, indexBuffer, finalIndices.size()};
}
struct SDLMeshDestroyContext {
Direct3DRMSDL3GPURenderer* renderer;
Uint32 id;
};
void Direct3DRMSDL3GPURenderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh)
{
auto* ctx = new SDLMeshDestroyContext{this, id};
mesh->AddDestroyCallback(
[](IDirect3DRMObject*, void* arg) {
auto* ctx = static_cast<SDLMeshDestroyContext*>(arg);
auto& cache = ctx->renderer->m_meshs[ctx->id];
SDL_ReleaseGPUBuffer(ctx->renderer->m_device, cache.vertexBuffer);
SDL_ReleaseGPUBuffer(ctx->renderer->m_device, cache.indexBuffer);
cache.meshGroup = nullptr;
delete ctx;
},
ctx
);
}
Uint32 Direct3DRMSDL3GPURenderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup)
{
for (Uint32 i = 0; i < m_meshs.size(); ++i) {
auto& cache = m_meshs[i];
if (cache.meshGroup == meshGroup) {
if (cache.version != meshGroup->version) {
SDL_ReleaseGPUBuffer(m_device, cache.vertexBuffer);
SDL_ReleaseGPUBuffer(m_device, cache.indexBuffer);
cache = std::move(UploadMesh(*meshGroup));
}
return i;
}
}
auto newCache = UploadMesh(*meshGroup);
for (Uint32 i = 0; i < m_meshs.size(); ++i) {
auto& cache = m_meshs[i];
if (!cache.meshGroup) {
cache = std::move(newCache);
AddMeshDestroyCallback(i, mesh);
return i;
}
}
m_meshs.push_back(std::move(newCache));
AddMeshDestroyCallback((Uint32) (m_meshs.size() - 1), mesh);
return (Uint32) (m_meshs.size() - 1);
}
void PackNormalMatrix(const Matrix3x3& normalMatrix3x3, D3DRMMATRIX4D& packedNormalMatrix4x4)
{
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
packedNormalMatrix4x4[col][row] = normalMatrix3x3[row][col];
}
packedNormalMatrix4x4[3][row] = 0.0f;
}
for (int col = 0; col < 4; ++col) {
packedNormalMatrix4x4[col][3] = 0.0f;
}
}
SDL_GPUTransferBuffer* Direct3DRMSDL3GPURenderer::GetUploadBuffer(size_t size)
{
WaitForPendingUpload();
if (m_uploadBufferSize < size) {
SDL_ReleaseGPUTransferBuffer(m_device, m_uploadBuffer);
SDL_GPUTransferBufferCreateInfo transferCreateInfo = {};
transferCreateInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
transferCreateInfo.size = size;
m_uploadBuffer = SDL_CreateGPUTransferBuffer(m_device, &transferCreateInfo);
if (!m_uploadBuffer) {
m_uploadBufferSize = 0;
SDL_LogError(
LOG_CATEGORY_MINIWIN,
"SDL_CreateGPUTransferBuffer failed for updating upload buffer (%s)",
SDL_GetError()
);
}
m_uploadBufferSize = size;
}
return m_uploadBuffer;
}
void Direct3DRMSDL3GPURenderer::StartRenderPass(float r, float g, float b, bool clear)
{
if (m_cmdbuf != nullptr) {
SDL_CancelGPUCommandBuffer(m_cmdbuf);
}
m_cmdbuf = SDL_AcquireGPUCommandBuffer(m_device);
if (!m_cmdbuf) {
SDL_LogError(
LOG_CATEGORY_MINIWIN,
"SDL_AcquireGPUCommandBuffer in StartRenderPass failed (%s)",
SDL_GetError()
);
return;
}
// Clear color and depth targets
SDL_GPUColorTargetInfo colorTargetInfo = {};
colorTargetInfo.texture = m_transferTexture;
colorTargetInfo.clear_color = {r, g, b, 1.0f};
colorTargetInfo.load_op = clear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_DONT_CARE;
SDL_GPUDepthStencilTargetInfo depthStencilTargetInfo = {};
depthStencilTargetInfo.texture = m_depthTexture;
depthStencilTargetInfo.clear_depth = 0.0f;
depthStencilTargetInfo.load_op = clear ? SDL_GPU_LOADOP_CLEAR : SDL_GPU_LOADOP_DONT_CARE;
depthStencilTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
m_renderPass = SDL_BeginGPURenderPass(m_cmdbuf, &colorTargetInfo, 1, &depthStencilTargetInfo);
}
void Direct3DRMSDL3GPURenderer::Clear(float r, float g, float b)
{
StartRenderPass(r, g, b, true);
}
HRESULT Direct3DRMSDL3GPURenderer::BeginFrame()
{
if (!m_renderPass) {
StartRenderPass(0, 0, 0, false);
}
SDL_BindGPUGraphicsPipeline(m_renderPass, m_opaquePipeline);
memcpy(&m_uniforms.projection, m_projection, sizeof(D3DRMMATRIX4D));
return DD_OK;
}
void Direct3DRMSDL3GPURenderer::EnableTransparency()
{
SDL_BindGPUGraphicsPipeline(m_renderPass, m_transparentPipeline);
}
void Direct3DRMSDL3GPURenderer::SubmitDraw(
DWORD meshId,
const D3DRMMATRIX4D& modelViewMatrix,
const D3DRMMATRIX4D& worldMatrix,
const D3DRMMATRIX4D& viewMatrix,
const Matrix3x3& normalMatrix,
const Appearance& appearance
)
{
memcpy(&m_uniforms.worldViewMatrix, modelViewMatrix, sizeof(D3DRMMATRIX4D));
PackNormalMatrix(normalMatrix, m_uniforms.normalMatrix);
m_fragmentShadingData.color = appearance.color;
m_fragmentShadingData.shininess = appearance.shininess;
bool useTexture = appearance.textureId != NO_TEXTURE_ID;
m_fragmentShadingData.useTexture = appearance.textureId != NO_TEXTURE_ID;
auto& mesh = m_meshs[meshId];
// Render the graphics
SDL_GPUTexture* texture = useTexture ? m_textures[appearance.textureId].gpuTexture : m_dummyTexture;
SDL_GPUTextureSamplerBinding samplerBinding = {texture, m_sampler};
SDL_BindGPUFragmentSamplers(m_renderPass, 0, &samplerBinding, 1);
SDL_PushGPUVertexUniformData(m_cmdbuf, 0, &m_uniforms, sizeof(m_uniforms));
SDL_PushGPUFragmentUniformData(m_cmdbuf, 0, &m_fragmentShadingData, sizeof(m_fragmentShadingData));
SDL_GPUBufferBinding vertexBufferBinding = {mesh.vertexBuffer};
SDL_BindGPUVertexBuffers(m_renderPass, 0, &vertexBufferBinding, 1);
SDL_GPUBufferBinding indexBufferBinding = {mesh.indexBuffer};
SDL_BindGPUIndexBuffer(m_renderPass, &indexBufferBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
SDL_DrawGPUIndexedPrimitives(m_renderPass, mesh.indexCount, 1, 0, 0, 0);
}
HRESULT Direct3DRMSDL3GPURenderer::FinalizeFrame()
{
return DD_OK;
}
void Direct3DRMSDL3GPURenderer::Resize(int width, int height, const ViewportTransform& viewportTransform)
{
m_width = width;
m_height = height;
m_viewportTransform = viewportTransform;
if (m_transferTexture) {
SDL_ReleaseGPUTexture(m_device, m_transferTexture);
}
SDL_GPUTextureCreateInfo textureInfo = {};
textureInfo.type = SDL_GPU_TEXTURETYPE_2D;
textureInfo.format = SDL_GetGPUSwapchainTextureFormat(m_device, DDWindow);
textureInfo.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
textureInfo.width = m_width;
textureInfo.height = m_height;
textureInfo.layer_count_or_depth = 1;
textureInfo.num_levels = 1;
textureInfo.sample_count = SDL_GPU_SAMPLECOUNT_1;
m_transferTexture = SDL_CreateGPUTexture(m_device, &textureInfo);
if (!m_transferTexture) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUTexture for backbuffer failed (%s)", SDL_GetError());
return;
}
if (m_depthTexture) {
SDL_ReleaseGPUTexture(m_device, m_depthTexture);
}
SDL_GPUTextureCreateInfo depthTexInfo = textureInfo;
depthTexInfo.format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
depthTexInfo.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
m_depthTexture = SDL_CreateGPUTexture(m_device, &depthTexInfo);
if (!m_depthTexture) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "SDL_CreateGPUTexture for depth buffer (%s)", SDL_GetError());
return;
}
// Setup texture GPU-to-CPU transfer
SDL_GPUTransferBufferCreateInfo downloadBufferInfo = {};
downloadBufferInfo.usage = SDL_GPU_TRANSFERBUFFERUSAGE_DOWNLOAD;
downloadBufferInfo.size =
((m_width - (m_viewportTransform.offsetX * 2)) * (m_height - (m_viewportTransform.offsetY * 2))) * 4;
m_downloadBuffer = SDL_CreateGPUTransferBuffer(m_device, &downloadBufferInfo);
if (!m_downloadBuffer) {
SDL_LogError(
LOG_CATEGORY_MINIWIN,
"SDL_CreateGPUTransferBuffer filed for download buffer (%s)",
SDL_GetError()
);
return;
}
}
void Direct3DRMSDL3GPURenderer::Flip()
{
if (!m_cmdbuf) {
return;
}
if (m_renderPass) {
SDL_EndGPURenderPass(m_renderPass);
m_renderPass = nullptr;
}
SDL_GPUTexture* swapchainTexture;
if (!SDL_WaitAndAcquireGPUSwapchainTexture(m_cmdbuf, DDWindow, &swapchainTexture, nullptr, nullptr) ||
!swapchainTexture) {
SDL_Log("SDL_WaitAndAcquireGPUSwapchainTexture: %s", SDL_GetError());
return;
}
SDL_GPUBlitInfo blit = {};
blit.source.texture = m_transferTexture;
blit.source.w = m_width;
blit.source.h = m_height;
blit.destination.texture = swapchainTexture;
blit.destination.w = m_width;
blit.destination.h = m_height;
blit.load_op = SDL_GPU_LOADOP_DONT_CARE;
blit.flip_mode = SDL_FLIP_NONE;
blit.filter = SDL_GPU_FILTER_NEAREST;
blit.cycle = false;
SDL_BlitGPUTexture(m_cmdbuf, &blit);
SDL_SubmitGPUCommandBuffer(m_cmdbuf);
m_cmdbuf = nullptr;
}
void Direct3DRMSDL3GPURenderer::Draw2DImage(
Uint32 textureId,
const SDL_Rect& srcRect,
const SDL_Rect& dstRect,
FColor color
)
{
if (!m_renderPass) {
StartRenderPass(0, 0, 0, false);
}
SDL_BindGPUGraphicsPipeline(m_renderPass, m_uiPipeline);
SDL_GPUTexture* tex;
SDL_Rect expandedDstRect;
if (textureId == NO_TEXTURE_ID) {
expandedDstRect = dstRect;
tex = m_dummyTexture;
}
else {
SDL3TextureCache& cache = m_textures[textureId];
tex = cache.gpuTexture;
auto surface = static_cast<DirectDrawSurfaceImpl*>(cache.texture->m_surface);
float scaleX = static_cast<float>(dstRect.w) / srcRect.w;
float scaleY = static_cast<float>(dstRect.h) / srcRect.h;
expandedDstRect = {
static_cast<int>(std::round(dstRect.x - srcRect.x * scaleX)),
static_cast<int>(std::round(dstRect.y - srcRect.y * scaleY)),
static_cast<int>(std::round(static_cast<float>(surface->m_surface->w) * scaleX)),
static_cast<int>(std::round(static_cast<float>(surface->m_surface->h) * scaleY)),
};
}
Create2DTransformMatrix(
expandedDstRect,
m_viewportTransform.scale,
m_viewportTransform.offsetX,
m_viewportTransform.offsetY,
m_uniforms.worldViewMatrix
);
CreateOrthographicProjection((float) m_width, (float) m_height, m_uniforms.projection);
SceneLight fullBright = {{1, 1, 1, 1}, {0, 0, 0}, 0, {0, 0, 0}, 0};
memcpy(&m_fragmentShadingData.lights, &fullBright, sizeof(SceneLight));
m_fragmentShadingData.lightCount = 1;
m_fragmentShadingData.color.r = static_cast<Uint8>(color.r * 255);
m_fragmentShadingData.color.g = static_cast<Uint8>(color.g * 255);
m_fragmentShadingData.color.b = static_cast<Uint8>(color.b * 255);
m_fragmentShadingData.color.a = static_cast<Uint8>(color.a * 255);
m_fragmentShadingData.shininess = 0.0f;
m_fragmentShadingData.useTexture = 1;
SDL_GPUTextureSamplerBinding samplerBinding = {tex, m_uiSampler};
SDL_BindGPUFragmentSamplers(m_renderPass, 0, &samplerBinding, 1);
SDL_PushGPUVertexUniformData(m_cmdbuf, 0, &m_uniforms, sizeof(m_uniforms));
SDL_PushGPUFragmentUniformData(m_cmdbuf, 0, &m_fragmentShadingData, sizeof(m_fragmentShadingData));
SDL_GPUBufferBinding vertexBufferBinding = {m_uiMeshCache.vertexBuffer};
SDL_BindGPUVertexBuffers(m_renderPass, 0, &vertexBufferBinding, 1);
SDL_GPUBufferBinding indexBufferBinding = {m_uiMeshCache.indexBuffer};
SDL_BindGPUIndexBuffer(m_renderPass, &indexBufferBinding, SDL_GPU_INDEXELEMENTSIZE_16BIT);
SDL_Rect scissor;
scissor.x = static_cast<int>(std::round(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX));
scissor.y = static_cast<int>(std::round(dstRect.y * m_viewportTransform.scale + m_viewportTransform.offsetY));
scissor.w = static_cast<int>(std::round(dstRect.w * m_viewportTransform.scale));
scissor.h = static_cast<int>(std::round(dstRect.h * m_viewportTransform.scale));
SDL_SetGPUScissor(m_renderPass, &scissor);
SDL_DrawGPUIndexedPrimitives(m_renderPass, m_uiMeshCache.indexCount, 1, 0, 0, 0);
SDL_Rect fullViewport = {0, 0, m_width, m_height};
SDL_SetGPUScissor(m_renderPass, &fullViewport);
}
void Direct3DRMSDL3GPURenderer::SetDither(bool dither)
{
}
void Direct3DRMSDL3GPURenderer::Download(SDL_Surface* target)
{
if (!m_cmdbuf) {
StartRenderPass(0, 0, 0, false);
}
if (m_renderPass) {
SDL_EndGPURenderPass(m_renderPass);
m_renderPass = nullptr;
}
const int offsetX = static_cast<int>(m_viewportTransform.offsetX);
const int offsetY = static_cast<int>(m_viewportTransform.offsetY);
const int width = m_width - offsetX * 2;
const int height = m_height - offsetY * 2;
SDL_GPUTextureRegion region = {};
region.texture = m_transferTexture;
region.x = offsetX;
region.y = offsetY;
region.w = width;
region.h = height;
region.d = 1;
SDL_GPUTextureTransferInfo transferInfo = {};
transferInfo.transfer_buffer = m_downloadBuffer;
SDL_GPUCopyPass* copyPass = SDL_BeginGPUCopyPass(m_cmdbuf);
SDL_DownloadFromGPUTexture(copyPass, &region, &transferInfo);
SDL_EndGPUCopyPass(copyPass);
WaitForPendingUpload();
SDL_GPUFence* fence = SDL_SubmitGPUCommandBufferAndAcquireFence(m_cmdbuf);
m_cmdbuf = nullptr;
if (!fence || !SDL_WaitForGPUFences(m_device, true, &fence, 1)) {
SDL_ReleaseGPUFence(m_device, fence);
return;
}
SDL_ReleaseGPUFence(m_device, fence);
void* downloadedData = SDL_MapGPUTransferBuffer(m_device, m_downloadBuffer, false);
if (!downloadedData) {
return;
}
SDL_Surface* renderedImage =
SDL_CreateSurfaceFrom(width, height, SDL_PIXELFORMAT_XRGB8888, downloadedData, width * 4);
SDL_BlitSurfaceScaled(renderedImage, nullptr, target, nullptr, SDL_SCALEMODE_NEAREST);
SDL_DestroySurface(renderedImage);
SDL_UnmapGPUTransferBuffer(m_device, m_downloadBuffer);
}