From 4abd404f2d940abc67287834e3060b78c0ace494 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Wed, 28 May 2025 22:04:52 +0200 Subject: [PATCH] Implement Software rendering --- miniwin/CMakeLists.txt | 1 + .../src/d3drm/backends/software/renderer.cpp | 236 ++++++++++++++++++ miniwin/src/d3drm/d3drm.cpp | 4 + miniwin/src/ddraw/ddraw.cpp | 23 +- miniwin/src/internal/d3drmrenderer_software.h | 41 +++ 5 files changed, 298 insertions(+), 7 deletions(-) create mode 100644 miniwin/src/d3drm/backends/software/renderer.cpp create mode 100644 miniwin/src/internal/d3drmrenderer_software.h diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 481b3479..958a9c26 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -18,6 +18,7 @@ add_library(miniwin STATIC EXCLUDE_FROM_ALL src/d3drm/d3drmviewport.cpp # D3DRM backends + src/d3drm/backends/software/renderer.cpp src/d3drm/backends/sdl3gpu/renderer.cpp src/d3drm/backends/sdl3gpu/shaders/generated/ShaderIndex.cpp ) diff --git a/miniwin/src/d3drm/backends/software/renderer.cpp b/miniwin/src/d3drm/backends/software/renderer.cpp new file mode 100644 index 00000000..8f01d472 --- /dev/null +++ b/miniwin/src/d3drm/backends/software/renderer.cpp @@ -0,0 +1,236 @@ +#include "d3drmrenderer.h" +#include "d3drmrenderer_software.h" + +#include +#include +#include +#include +#include + +Direct3DRMSoftwareRenderer::Direct3DRMSoftwareRenderer(DWORD width, DWORD height) : m_width(width), m_height(height) +{ +} + +void Direct3DRMSoftwareRenderer::SetBackbuffer(SDL_Surface* buf) +{ + m_backbuffer = buf; + if (m_backbuffer) { + m_zBuffer.resize(m_width * m_height); + std::fill(m_zBuffer.begin(), m_zBuffer.end(), std::numeric_limits::infinity()); + } +} + +void Direct3DRMSoftwareRenderer::PushVertices(const PositionColorVertex* vertices, size_t count) +{ + m_vertexBuffer.insert(m_vertexBuffer.end(), vertices, vertices + count); +} + +void Direct3DRMSoftwareRenderer::SetProjection(D3DRMMATRIX4D perspective, D3DVALUE front, D3DVALUE back) +{ + m_front = front; + m_back = back; + memcpy(proj, perspective, sizeof(proj)); +} + +void Direct3DRMSoftwareRenderer::ClearZBuffer() +{ + std::fill(m_zBuffer.begin(), m_zBuffer.end(), std::numeric_limits::infinity()); +} + +void Direct3DRMSoftwareRenderer::ProjectVertex(const PositionColorVertex& v, float& out_x, float& out_y, float& out_z) + const +{ + float px = proj[0][0] * v.x + proj[1][0] * v.y + proj[2][0] * v.z + proj[3][0]; + float py = proj[0][1] * v.x + proj[1][1] * v.y + proj[2][1] * v.z + proj[3][1]; + float pz = proj[0][2] * v.x + proj[1][2] * v.y + proj[2][2] * v.z + proj[3][2]; + float pw = proj[0][3] * v.x + proj[1][3] * v.y + proj[2][3] * v.z + proj[3][3]; + + // Perspective divide + if (pw != 0.0f) { + px /= pw; + py /= pw; + pz /= pw; + } + + // Map from NDC [-1,1] to screen coordinates + out_x = (px * 0.5f + 0.5f) * m_width; + out_y = (1.0f - (py * 0.5f + 0.5f)) * m_height; + out_z = pz; +} + +PositionColorVertex SplitEdge(PositionColorVertex a, const PositionColorVertex& b, float plane) +{ + float t = (plane - a.z) / (b.z - a.z); + a.x = a.x + t * (b.x - a.x); + a.y = a.y + t * (b.y - a.y); + a.z = plane; + return a; +} + +void Direct3DRMSoftwareRenderer::DrawTriangleClipped( + const PositionColorVertex& v0, + const PositionColorVertex& v1, + const PositionColorVertex& v2 +) +{ + bool in0 = v0.z >= m_front; + bool in1 = v1.z >= m_front; + bool in2 = v2.z >= m_front; + + int insideCount = in0 + in1 + in2; + + if (insideCount == 0) { + return; + } + + if (insideCount == 3) { + DrawTriangleProjected(v0, v1, v2); + } + else if (insideCount == 2) { + PositionColorVertex split; + if (!in0) { + split = SplitEdge(v2, v0, m_front); + DrawTriangleProjected(v1, v2, split); + DrawTriangleProjected(v1, split, SplitEdge(v1, v0, m_front)); + } + else if (!in1) { + split = SplitEdge(v0, v1, m_front); + DrawTriangleProjected(v2, v0, split); + DrawTriangleProjected(v2, split, SplitEdge(v2, v1, m_front)); + } + else { + split = SplitEdge(v1, v2, m_front); + DrawTriangleProjected(v0, v1, split); + DrawTriangleProjected(v0, split, SplitEdge(v0, v2, m_front)); + } + } + else if (in0) { + DrawTriangleProjected(v0, SplitEdge(v0, v1, m_front), SplitEdge(v0, v2, m_front)); + } + else if (in1) { + DrawTriangleProjected(SplitEdge(v1, v0, m_front), v1, SplitEdge(v1, v2, m_front)); + } + else { + DrawTriangleProjected(SplitEdge(v2, v0, m_front), SplitEdge(v2, v1, m_front), v2); + } +} + +void Direct3DRMSoftwareRenderer::DrawTriangleProjected( + const PositionColorVertex& v0, + const PositionColorVertex& v1, + const PositionColorVertex& v2 +) +{ + float x0, y0, z0, x1, y1, z1, x2, y2, z2; + ProjectVertex(v0, x0, y0, z0); + ProjectVertex(v1, x1, y1, z1); + ProjectVertex(v2, x2, y2, z2); + + // Skip triangles outside the frustum + if ((z0 < m_front && z1 < m_front && z2 < m_front) || (z0 > m_back && z1 > m_back && z2 > m_back)) { + return; + } + + // Skip offscreen triangles + if ((x0 < 0 && x1 < 0 && x2 < 0) || (x0 >= m_width && x1 >= m_width && x2 >= m_width) || + (y0 < 0 && y1 < 0 && y2 < 0) || (y0 >= m_height && y1 >= m_height && y2 >= m_height)) { + return; + } + + int minX = std::max(0, (int) std::floor(std::min({x0, x1, x2}))); + int maxX = std::min((int) m_width - 1, (int) std::ceil(std::max({x0, x1, x2}))); + int minY = std::max(0, (int) std::floor(std::min({y0, y1, y2}))); + int maxY = std::min((int) m_height - 1, (int) std::ceil(std::max({y0, y1, y2}))); + if (minX > maxX || minY > maxY) { + return; + } + + auto edge = [](float x0, float y0, float x1, float y1, float x, float y) { + return (x - x0) * (y1 - y0) - (y - y0) * (x1 - x0); + }; + float area = edge(x0, y0, x1, y1, x2, y2); + if (area >= 0) { + return; + } + float invArea = 1.0f / area; + + const SDL_PixelFormatDetails* format = SDL_GetPixelFormatDetails(m_backbuffer->format); + Uint32 color = SDL_MapRGBA(format, nullptr, v0.r, v0.g, v0.b, v0.a); + + int bytesPerPixel = format->bits_per_pixel / 8; + Uint8* pixels = (Uint8*) m_backbuffer->pixels; + int pitch = m_backbuffer->pitch; + + for (int y = minY; y <= maxY; ++y) { + for (int x = minX; x <= maxX; ++x) { + float px = x + 0.5f; + float py = y + 0.5f; + float w0 = edge(x1, y1, x2, y2, px, py) * invArea; + if (w0 < 0.0f || w0 > 1.0f) { + continue; + } + + float w1 = edge(x2, y2, x0, y0, px, py) * invArea; + if (w1 < 0.0f || w1 > 1.0f - w0) { + continue; + } + + float w2 = 1.0f - w0 - w1; + float z = w0 * z0 + w1 * z1 + w2 * z2; + + int zidx = y * m_width + x; + if (z >= m_zBuffer[zidx]) { + continue; + } + + m_zBuffer[zidx] = z; + Uint8* pixelAddr = pixels + y * pitch + x * bytesPerPixel; + // TODO make color endian safe + memcpy(pixelAddr, &color, bytesPerPixel); + } + } +} + +DWORD Direct3DRMSoftwareRenderer::GetWidth() +{ + return m_width; +} + +DWORD Direct3DRMSoftwareRenderer::GetHeight() +{ + return m_height; +} + +void Direct3DRMSoftwareRenderer::GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) +{ + memset(halDesc, 0, sizeof(D3DDEVICEDESC)); + + helDesc->dcmColorModel = D3DCOLORMODEL::RGB; + helDesc->dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + helDesc->dwDeviceZBufferBitDepth = DDBD_32; + helDesc->dwDeviceRenderBitDepth = DDBD_16 | DDBD_24 | DDBD_32; + helDesc->dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + helDesc->dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + helDesc->dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; +} + +const char* Direct3DRMSoftwareRenderer::GetName() +{ + return "Software Rendere"; +} + +HRESULT Direct3DRMSoftwareRenderer::Render() +{ + if (!m_backbuffer || m_vertexBuffer.size() % 3 != 0 || !SDL_LockSurface(m_backbuffer)) { + return DDERR_GENERIC; + } + ClearZBuffer(); + for (size_t i = 0; i + 2 < m_vertexBuffer.size(); i += 3) { + DrawTriangleClipped(m_vertexBuffer[i], m_vertexBuffer[i + 1], m_vertexBuffer[i + 2]); + } + SDL_UnlockSurface(m_backbuffer); + + m_vertexBuffer.clear(); + + return DD_OK; +} diff --git a/miniwin/src/d3drm/d3drm.cpp b/miniwin/src/d3drm/d3drm.cpp index 1464929c..f7044554 100644 --- a/miniwin/src/d3drm/d3drm.cpp +++ b/miniwin/src/d3drm/d3drm.cpp @@ -8,6 +8,7 @@ #include "d3drmobject_impl.h" #include "d3drmrenderer.h" #include "d3drmrenderer_sdl3gpu.h" +#include "d3drmrenderer_software.h" #include "d3drmtexture_impl.h" #include "d3drmviewport_impl.h" #include "ddraw_impl.h" @@ -137,6 +138,9 @@ HRESULT Direct3DRMImpl::CreateDeviceFromSurface( if (SDL_memcmp(&guid, &SDL3_GPU_GUID, sizeof(GUID)) == 0) { renderer = Direct3DRMSDL3GPURenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } + else if (SDL_memcmp(&guid, &SOFTWARE_GUID, sizeof(GUID)) == 0) { + renderer = new Direct3DRMSoftwareRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Device GUID not recognized"); return E_NOINTERFACE; diff --git a/miniwin/src/ddraw/ddraw.cpp b/miniwin/src/ddraw/ddraw.cpp index c8d81fe4..31157378 100644 --- a/miniwin/src/ddraw/ddraw.cpp +++ b/miniwin/src/ddraw/ddraw.cpp @@ -1,4 +1,5 @@ #include "d3drmrenderer_sdl3gpu.h" +#include "d3drmrenderer_software.h" #include "ddpalette_impl.h" #include "ddraw_impl.h" #include "ddsurface_impl.h" @@ -208,14 +209,8 @@ HRESULT DirectDrawImpl::GetCaps(LPDDCAPS lpDDDriverCaps, LPDDCAPS lpDDHELCaps) return S_OK; } -HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +void EnumDevice(LPD3DENUMDEVICESCALLBACK cb, void* ctx, Direct3DRMRenderer* device, GUID deviceGuid) { - auto device = Direct3DRMSDL3GPURenderer::Create(640, 480); - if (!device) { - return S_OK; - } - - GUID deviceGuid = SDL3_GPU_GUID; D3DDEVICEDESC halDesc = {}; D3DDEVICEDESC helDesc = {}; device->GetDesc(&halDesc, &helDesc); @@ -224,6 +219,17 @@ HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) cb(&deviceGuid, deviceNameDup, deviceDescDup, &halDesc, &helDesc, ctx); SDL_free(deviceDescDup); SDL_free(deviceNameDup); +} + +HRESULT DirectDrawImpl::EnumDevices(LPD3DENUMDEVICESCALLBACK cb, void* ctx) +{ + Direct3DRMRenderer* device = Direct3DRMSDL3GPURenderer::Create(640, 480); + if (device) { + EnumDevice(cb, ctx, device, SDL3_GPU_GUID); + delete device; + } + device = new Direct3DRMSoftwareRenderer(640, 480); + EnumDevice(cb, ctx, device, SOFTWARE_GUID); delete device; return S_OK; @@ -314,6 +320,9 @@ HRESULT DirectDrawImpl::CreateDevice( if (SDL_memcmp(&guid, &SDL3_GPU_GUID, sizeof(GUID)) == 0) { renderer = Direct3DRMSDL3GPURenderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight); } + else if (SDL_memcmp(&guid, &SOFTWARE_GUID, sizeof(GUID)) == 0) { + renderer = new Direct3DRMSoftwareRenderer(DDSDesc.dwWidth, DDSDesc.dwHeight); + } else { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Device GUID not recognized"); return E_NOINTERFACE; diff --git a/miniwin/src/internal/d3drmrenderer_software.h b/miniwin/src/internal/d3drmrenderer_software.h new file mode 100644 index 00000000..9e3cfcc2 --- /dev/null +++ b/miniwin/src/internal/d3drmrenderer_software.h @@ -0,0 +1,41 @@ +#pragma once + +#include "d3drmrenderer.h" + +#include +#include +#include + +DEFINE_GUID(SOFTWARE_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02); + +class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer { +public: + Direct3DRMSoftwareRenderer(DWORD width, DWORD height); + void SetBackbuffer(SDL_Surface* backbuffer) override; + void PushVertices(const PositionColorVertex* vertices, size_t count) override; + void SetProjection(D3DRMMATRIX4D perspective, D3DVALUE front, D3DVALUE back) override; + DWORD GetWidth() override; + DWORD GetHeight() override; + void GetDesc(D3DDEVICEDESC* halDesc, D3DDEVICEDESC* helDesc) override; + const char* GetName() override; + HRESULT Render() override; + +private: + void ClearZBuffer(); + void DrawTriangleProjected(const PositionColorVertex&, const PositionColorVertex&, const PositionColorVertex&); + void DrawTriangleClipped( + const PositionColorVertex& v0, + const PositionColorVertex& v1, + const PositionColorVertex& v2 + ); + void ProjectVertex(const PositionColorVertex&, float&, float&, float&) const; + + DWORD m_width; + DWORD m_height; + SDL_Surface* m_backbuffer = nullptr; + D3DVALUE m_front; + D3DVALUE m_back; + std::vector m_vertexBuffer; + float proj[4][4] = {0}; + std::vector m_zBuffer; +};