Add texture rendering to the software rendere (#211)

This commit is contained in:
Anders Jenbo 2025-06-01 05:21:59 +02:00 committed by GitHub
parent 403ead7453
commit ec7204ed38
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 125 additions and 66 deletions

View File

@ -191,7 +191,7 @@ void Direct3DRMSDL3GPURenderer::SetBackbuffer(SDL_Surface* buf)
void Direct3DRMSDL3GPURenderer::PushLights(const SceneLight* vertices, size_t count)
{
if (count > 3) {
SDL_LogError(LOG_CATEGORY_MINIWIN, "Unsupported number of lights (%d)", count);
SDL_LogError(LOG_CATEGORY_MINIWIN, "Unsupported number of lights (%d)", static_cast<int>(count));
count = 3;
}
memcpy(&m_lights.lights, vertices, sizeof(SceneLight) * count);

View File

@ -42,17 +42,18 @@ void Direct3DRMSoftwareRenderer::SetProjection(D3DRMMATRIX4D perspective, D3DVAL
void Direct3DRMSoftwareRenderer::ClearZBuffer()
{
std::fill(m_zBuffer.begin(), m_zBuffer.end(), std::numeric_limits<double>::infinity());
std::fill(m_zBuffer.begin(), m_zBuffer.end(), std::numeric_limits<float>::infinity());
}
void Direct3DRMSoftwareRenderer::ProjectVertex(const PositionColorVertex& v, float& out_x, float& out_y, float& out_z)
const
void Direct3DRMSoftwareRenderer::ProjectVertex(const PositionColorVertex& v, D3DRMVECTOR4D& p) 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];
p.w = pw;
// Perspective divide
if (pw != 0.0f) {
px /= pw;
@ -61,9 +62,9 @@ void Direct3DRMSoftwareRenderer::ProjectVertex(const PositionColorVertex& v, flo
}
// 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;
p.x = (px * 0.5f + 0.5f) * m_width;
p.y = (1.0f - (py * 0.5f + 0.5f)) * m_height;
p.z = pz;
}
PositionColorVertex SplitEdge(PositionColorVertex a, const PositionColorVertex& b, float plane)
@ -72,6 +73,21 @@ PositionColorVertex SplitEdge(PositionColorVertex a, const PositionColorVertex&
a.x = a.x + t * (b.x - a.x);
a.y = a.y + t * (b.y - a.y);
a.z = plane;
a.u = a.u + t * (b.u - a.u);
a.v = a.v + t * (b.v - a.v);
a.nx = a.nx + t * (b.nx - a.nx);
a.ny = a.ny + t * (b.ny - a.ny);
a.nz = a.nz + t * (b.nz - a.nz);
float len = std::sqrt(a.nx * a.nx + a.ny * a.ny + a.nz * a.nz);
if (len > 0.0001f) {
a.nx /= len;
a.ny /= len;
a.nz /= len;
}
return a;
}
@ -203,26 +219,27 @@ void Direct3DRMSoftwareRenderer::DrawTriangleProjected(
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);
D3DRMVECTOR4D p0, p1, p2;
ProjectVertex(v0, p0);
ProjectVertex(v1, p1);
ProjectVertex(v2, p2);
// Skip triangles outside the frustum
if ((z0 < m_front && z1 < m_front && z2 < m_front) || (z0 > m_back && z1 > m_back && z2 > m_back)) {
if ((p0.z < m_front && p1.z < m_front && p2.z < m_front) || (p0.z > m_back && p1.z > m_back && p2.z > 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)) {
if ((p0.x < 0 && p1.x < 0 && p2.x < 0) || (p0.x >= m_width && p1.x >= m_width && p2.x >= m_width) ||
(p0.y < 0 && p1.y < 0 && p2.y < 0) || (p0.y >= m_height && p1.y >= m_height && p2.y >= 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})));
int minX = std::max(0, (int) std::floor(std::min({p0.x, p1.x, p2.x})));
int maxX = std::min((int) m_width - 1, (int) std::ceil(std::max({p0.x, p1.x, p2.x})));
int minY = std::max(0, (int) std::floor(std::min({p0.y, p1.y, p2.y})));
int maxY = std::min((int) m_height - 1, (int) std::ceil(std::max({p0.y, p1.y, p2.y})));
if (minX > maxX || minY > maxY) {
return;
}
@ -230,40 +247,29 @@ void Direct3DRMSoftwareRenderer::DrawTriangleProjected(
auto edge = [](double x0, double y0, double x1, double y1, double x, double y) {
return (x - x0) * (y1 - y0) - (y - y0) * (x1 - x0);
};
double area = edge(x0, y0, x1, y1, x2, y2);
float area = edge(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y);
if (area >= 0) {
return;
}
double invArea = 1.0f / area;
float invArea = 1.0f / area;
// Per-vertex lighting using vertex normals
SDL_Color c0 = ApplyLighting(v0);
SDL_Color c1 = ApplyLighting(v1);
SDL_Color c2 = ApplyLighting(v2);
SDL_Surface* texture = nullptr;
Uint32 texId = v0.texId;
int texturePitch;
Uint8* texels = nullptr;
int texWidthScale;
int texHeightScale;
if (texId != NO_TEXTURE_ID) {
texture = m_textures[texId];
if (texture && SDL_LockSurface(texture)) {
// Pointer to first pixel data
Uint8* pixelAddr = static_cast<Uint8*>(texture->pixels);
Uint32 pixel;
memcpy(&pixel, pixelAddr, m_bytesPerPixel);
Uint8 r, g, b, a;
SDL_GetRGBA(pixel, m_format, m_palette, &r, &g, &b, &a);
// TODO use the UV to read out and blend texels on the triangle
c0.r = r;
c0.g = g;
c0.b = b;
c0.a = a;
c1 = c0;
c2 = c0;
SDL_UnlockSurface(texture);
SDL_Surface* texture = m_textures[texId].cached;
if (texture) {
texturePitch = texture->pitch;
texels = static_cast<Uint8*>(texture->pixels);
texWidthScale = texture->w - 1;
texHeightScale = texture->h - 1;
}
}
@ -272,23 +278,23 @@ void Direct3DRMSoftwareRenderer::DrawTriangleProjected(
for (int y = minY; y <= maxY; ++y) {
for (int x = minX; x <= maxX; ++x) {
double px = x + 0.5f;
double py = y + 0.5f;
double w0 = edge(x1, y1, x2, y2, px, py) * invArea;
float px = x + 0.5f;
float py = y + 0.5f;
float w0 = edge(p1.x, p1.y, p2.x, p2.y, px, py) * invArea;
if (w0 < 0.0f || w0 > 1.0f) {
continue;
}
double w1 = edge(x2, y2, x0, y0, px, py) * invArea;
float w1 = edge(p2.x, p2.y, p0.x, p0.y, px, py) * invArea;
if (w1 < 0.0f || w1 > 1.0f - w0) {
continue;
}
double w2 = 1.0f - w0 - w1;
double z = w0 * z0 + w1 * z1 + w2 * z2;
float w2 = 1.0f - w0 - w1;
float z = w0 * p0.z + w1 * p1.z + w2 * p2.z;
int zidx = y * m_width + x;
double& zref = m_zBuffer[zidx];
float& zref = m_zBuffer[zidx];
if (z >= zref) {
continue;
}
@ -301,10 +307,43 @@ void Direct3DRMSoftwareRenderer::DrawTriangleProjected(
if (v0.a == 255) {
zref = z;
if (texels) {
// Perspective correct interpolate texture coords
float invW = w0 / p0.w + w1 / p1.w + w2 / p2.w;
if (invW == 0.0) {
continue;
}
invW = 1.0 / invW;
float u = static_cast<float>(((w0 * v0.u / p0.w) + (w1 * v1.u / p1.w) + (w2 * v2.u / p2.w)) * invW);
float v = static_cast<float>(((w0 * v0.v / p0.w) + (w1 * v1.v / p1.w) + (w2 * v2.v / p2.w)) * invW);
// Tile textures
u = u - std::floor(u);
v = v - std::floor(v);
int texX = static_cast<int>(u * texWidthScale);
int texY = static_cast<int>(v * texHeightScale);
Uint8* texelAddr = texels + texY * texturePitch + texX * m_bytesPerPixel;
Uint32 texelColor = 0;
memcpy(&texelColor, texelAddr, m_bytesPerPixel);
Uint8 tr, tg, tb, ta;
SDL_GetRGBA(texelColor, m_format, m_palette, &tr, &tg, &tb, &ta);
// Multiply vertex color by texel color
r = (r * tr + 127) / 255;
g = (g * tg + 127) / 255;
b = (b * tb + 127) / 255;
}
Uint32 finalColor = SDL_MapRGBA(m_format, m_palette, r, g, b, 255);
memcpy(pixelAddr, &finalColor, m_bytesPerPixel);
}
else {
// Transparent alpha blending with vertex alpha
BlendPixel(pixelAddr, r, g, b, v0.a);
}
}
@ -322,9 +361,13 @@ void Direct3DRMSoftwareRenderer::AddTextureDestroyCallback(Uint32 id, IDirect3DR
texture->AddDestroyCallback(
[](IDirect3DRMObject* obj, void* arg) {
auto* ctx = static_cast<TextureDestroyContext*>(arg);
auto& sufRef = ctx->renderer->m_textures[ctx->textureId];
SDL_DestroySurface(sufRef);
sufRef = nullptr;
auto& cacheEntry = ctx->renderer->m_textures[ctx->textureId];
if (cacheEntry.cached) {
SDL_UnlockSurface(cacheEntry.cached);
SDL_DestroySurface(cacheEntry.cached);
cacheEntry.cached = nullptr;
cacheEntry.texture = nullptr;
}
delete ctx;
},
ctx
@ -335,29 +378,40 @@ Uint32 Direct3DRMSoftwareRenderer::GetTextureId(IDirect3DRMTexture* iTexture)
{
auto texture = static_cast<Direct3DRMTextureImpl*>(iTexture);
auto surface = static_cast<DirectDrawSurfaceImpl*>(texture->m_surface);
SDL_Surface* convertedRender = SDL_ConvertSurface(surface->m_surface, m_backbuffer->format);
// Check if already mapped
for (Uint32 i = 0; i < m_textures.size(); ++i) {
if (m_textures[i] == convertedRender) {
auto& texRef = m_textures[i];
if (texRef.texture == texture) {
if (texRef.version != texture->m_version) {
// Update animated textures
SDL_DestroySurface(texRef.cached);
texRef.cached = SDL_ConvertSurface(surface->m_surface, m_backbuffer->format);
SDL_LockSurface(texRef.cached);
texRef.version = texture->m_version;
}
return i;
}
}
SDL_Surface* convertedRender = SDL_ConvertSurface(surface->m_surface, m_backbuffer->format);
SDL_LockSurface(convertedRender);
// Reuse freed slot
for (Uint32 i = 0; i < m_textures.size(); ++i) {
auto& texRef = m_textures[i];
if (texRef == nullptr) {
texRef = convertedRender;
if (texRef.texture == nullptr) {
texRef.texture = texture;
texRef.cached = convertedRender;
AddTextureDestroyCallback(i, texture);
return i;
}
}
// Append new
Uint32 newId = static_cast<Uint32>(m_textures.size());
m_textures.push_back(convertedRender);
AddTextureDestroyCallback(newId, texture);
return newId;
m_textures.push_back({texture, texture->m_version, convertedRender});
AddTextureDestroyCallback(static_cast<Uint32>(m_textures.size() - 1), texture);
return static_cast<Uint32>(m_textures.size() - 1);
}
DWORD Direct3DRMSoftwareRenderer::GetWidth()

View File

@ -108,8 +108,6 @@ D3DVECTOR ComputeTriangleNormal(const D3DVECTOR& v0, const D3DVECTOR& v1, const
HRESULT Direct3DRMViewportImpl::CollectSceneData()
{
MINIWIN_NOT_IMPLEMENTED(); // Textures, Materials
m_backgroundColor = static_cast<Direct3DRMFrameImpl*>(m_rootFrame)->m_backgroundColor;
std::vector<SceneLight> lights;
@ -285,6 +283,8 @@ HRESULT Direct3DRMViewportImpl::CollectSceneData()
vtx.b = (color >> 0) & 0xFF;
vtx.a = (color >> 24) & 0xFF;
vtx.texId = texId;
vtx.u = dv.tu;
vtx.v = dv.tv;
verts.push_back(vtx);
}
}

View File

@ -11,6 +11,7 @@ typedef struct PositionColorVertex {
float nx, ny, nz;
Uint8 r, g, b, a;
Uint32 texId = NO_TEXTURE_ID;
float u, v;
} PositionColorVertex;
struct FColor {

View File

@ -9,6 +9,12 @@
DEFINE_GUID(SOFTWARE_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02);
struct TextureCache {
Direct3DRMTextureImpl* texture;
Uint8 version;
SDL_Surface* cached;
};
class Direct3DRMSoftwareRenderer : public Direct3DRMRenderer {
public:
Direct3DRMSoftwareRenderer(DWORD width, DWORD height);
@ -31,7 +37,7 @@ private:
const PositionColorVertex& v1,
const PositionColorVertex& v2
);
void ProjectVertex(const PositionColorVertex&, float&, float&, float&) const;
void ProjectVertex(const PositionColorVertex& v, D3DRMVECTOR4D& p) const;
void BlendPixel(Uint8* pixelAddr, Uint8 r, Uint8 g, Uint8 b, Uint8 a);
SDL_Color ApplyLighting(const PositionColorVertex& vertex);
void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture);
@ -43,10 +49,10 @@ private:
const SDL_PixelFormatDetails* m_format;
int m_bytesPerPixel;
std::vector<SceneLight> m_lights;
std::vector<SDL_Surface*> m_textures;
std::vector<TextureCache> m_textures;
D3DVALUE m_front;
D3DVALUE m_back;
std::vector<PositionColorVertex> m_vertexBuffer;
float proj[4][4] = {0};
std::vector<double> m_zBuffer;
std::vector<float> m_zBuffer;
};

View File

@ -9,7 +9,5 @@ struct Direct3DRMTextureImpl : public Direct3DRMObjectBaseImpl<IDirect3DRMTextur
HRESULT Changed(BOOL pixels, BOOL palette) override;
IDirectDrawSurface* m_surface = nullptr;
private:
Uint8 m_version = 0;
};