From 79c8fd7fb9004a79f3a1d6cac1fcd46df1dc3c92 Mon Sep 17 00:00:00 2001 From: Ivan Ushakov Date: Sun, 26 Jul 2026 10:13:11 +0300 Subject: [PATCH] Camera: draw the letterbox bars again, through the 2D batch CameraManager::drawBlackBars opened with if(!gb_RenderDevice3D) // world 3D render not ported to SDL backend yet (slice 3) return; and nothing has assigned gb_RenderDevice3D since the D3D9 retirement, so the bars have been dead on every platform, Windows included. The comment was stale twice over: the world 3D render has long since landed, and what actually held this back was the dynamic vertex buffer family (Render-PORTING.md #15) -- GetBufferXYZWD() returns nullptr on cSDLRenderDevice, and cVertexBufferInternal::Lock/Unlock/ DrawPrimitive are empty bodies in RenderStub.cpp. Dropping the guard on its own would have turned a silent no-op into a null dereference. But the bars never needed a dynamic buffer of their own. The original hand-rolled SetNoMaterial + GetBufferXYZWD() + PT_TRIANGLELIST is exactly what cD3DRender::DrawRectangle queued into `rectangles` and FlushFilledRect emptied at EndScene -- same buffer, same primitive, same vertex format. So the port is four DrawRectangle calls, which cSDLRenderDevice already forwards to the UI batch. This is the third time #15 has looked like a blocker and turned out not to be; the register's own warning applies. Going through DrawRectangle drops the state dance with it. The six Get/SetRenderState calls existed to turn off depth test, alpha test and blending and put them back; the 2D pipeline has none of them to turn off. SetNoMaterial goes too -- DrawRectangle passes a null texture, and the UI renderer binds its 1x1 white, so the vertex colour comes through unmodulated. That leaves this file with no D3D names at all, so the Render/D3D/D3DRender.h include goes as well. Two deliberate divergences, both commented at the call site: Opacity now works. D3D drew the bars with D3DRS_ALPHABLENDENABLE FALSE, so the alpha in Color4c(0,0,0, opacity*255) never reached the blender and the bars were solid black whatever was passed. The 2D pipeline blends. The only caller (GameShell.cpp) takes the default 1.0f, so shipped content is unchanged; the parameter just stops being a lie. DrawRectangle is integer and the rects are not, so they expand to the enclosing integer rect rather than truncating. The four rects are laid out such that this pushes every inner edge outward. A bar can now overshoot the work area by a pixel, which is invisible; truncating could leave a 1px lit seam of scene along the inner edge, which is not. Only visible below 4:3 -- drawBlackBars returns early above it, and there the bars are zero-sized anyway. Of the shipped resolutions, 1280x1024, 1024x800 and 720x576 qualify; at 1280x1024 the work area is 1280x960 centred, giving a 32px bar top and bottom. Not visually verified: invisible at any widescreen setting. Co-Authored-By: Claude Opus 5 --- Game/CameraManager.cpp | 51 ++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/Game/CameraManager.cpp b/Game/CameraManager.cpp index 716f1311..8f2feb33 100644 --- a/Game/CameraManager.cpp +++ b/Game/CameraManager.cpp @@ -15,7 +15,6 @@ #include "Serialization/Serialization.h" #include "Water/Water.h" #include "Environment/Environment.h" -#include "Render/D3D/D3DRender.h" #include "Render/src/cCamera.h" #include "Render/src/Scene.h" #include "Serialization/SerializationFactory.h" @@ -1639,20 +1638,6 @@ void CameraManager::drawBlackBars(float opacity) if(isUnderEditor() || aspect() - FLT_COMPARE_TOLERANCE > 4.0f / 3.0f) return; - if(!gb_RenderDevice3D) // world 3D render not ported to SDL backend yet (slice 3) - return; - - gb_RenderDevice->SetNoMaterial(ALPHA_BLEND, MatXf::ID); - - int oldAlphaBlend = gb_RenderDevice3D->GetRenderState(D3DRS_ALPHABLENDENABLE); - gb_RenderDevice3D->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); - - int oldAlphaTest = gb_RenderDevice3D->GetRenderState(D3DRS_ALPHATESTENABLE); - gb_RenderDevice3D->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); - - int oldZEnable = gb_RenderDevice3D->GetRenderState(D3DRS_ZENABLE); - gb_RenderDevice3D->SetRenderState(D3DRS_ZENABLE, FALSE); - float topSize = 0.5f + frustumClip().ymin(); float bottomSize = 0.5f - frustumClip().ymax(); @@ -1671,28 +1656,26 @@ void CameraManager::drawBlackBars(float opacity) }; const int numRects = sizeof(rectangles) / sizeof(rectangles[0]); - const int numPoints = numRects * 2 * 3; Color4c color(0, 0, 0, round(opacity * 255.0f)); - int npoint = 0; - cVertexBuffer* buffer = gb_RenderDevice->GetBufferXYZWD(); - sVertexXYZWD* pv = buffer->Lock(numPoints); + // The bars ride the 2D batch, which is where D3D drew them too: SetNoMaterial + + // GetBufferXYZWD() + PT_TRIANGLELIST is exactly what cD3DRender::DrawRectangle queued + // into `rectangles` and FlushFilledRect emptied at EndScene. Going through + // DrawRectangle instead of building the vertices by hand drops the whole + // GetRenderState/SetRenderState dance with it: the 2D pipeline has no depth test and + // no alpha test to turn off, and no world matrix to reset. + // + // One deliberate divergence: D3D drew these with D3DRS_ALPHABLENDENABLE FALSE, so the + // alpha in `color` never reached the blender and `opacity` did nothing -- the bars were + // solid black whatever was passed. The 2D pipeline blends, so opacity now works as its + // name says. The only caller takes the default 1.0f, so shipped content is unchanged. for(BlackRectangle* it = &rectangles[0]; it != &rectangles[0] + numRects; ++it){ BlackRectangle& p = *it; - - pv[0].x=p.x1; pv[0].y=p.y1; pv[0].z=0.001f; pv[0].w=0.001f; pv[0].diffuse = color; - pv[1].x=p.x1; pv[1].y=p.y2; pv[1].z=0.001f; pv[1].w=0.001f; pv[1].diffuse = color; - pv[2].x=p.x2; pv[2].y=p.y1; pv[2].z=0.001f; pv[2].w=0.001f; pv[2].diffuse = color; - - pv[3].x=p.x2; pv[3].y=p.y1; pv[3].z=0.001f; pv[3].w=0.001f; pv[3].diffuse = color; - pv[4].x=p.x1; pv[4].y=p.y2; pv[4].z=0.001f; pv[4].w=0.001f; pv[4].diffuse = color; - pv[5].x=p.x2; pv[5].y=p.y2; pv[5].z=0.001f; pv[5].w=0.001f; pv[5].diffuse = color; - pv += 6; + // DrawRectangle is integer; the rects are not. Expand to the enclosing integer rect + // rather than truncating, so a bar can only overshoot the work area by a pixel, + // never leave a lit seam of scene showing along its inner edge. + const int x1 = (int)floor(p.x1), y1 = (int)floor(p.y1); + const int x2 = (int)ceil(p.x2), y2 = (int)ceil(p.y2); + gb_RenderDevice->DrawRectangle(x1, y1, x2 - x1, y2 - y1, color); } - buffer->Unlock(numPoints); - buffer->DrawPrimitive(PT_TRIANGLELIST, numPoints / 3); - - gb_RenderDevice3D->SetRenderState(D3DRS_ALPHABLENDENABLE, oldAlphaBlend); - gb_RenderDevice3D->SetRenderState(D3DRS_ALPHATESTENABLE, oldAlphaTest); - gb_RenderDevice3D->SetRenderState(D3DRS_ZENABLE, oldZEnable); }