1 // Copyright 2009 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4
5 #include "VideoCommon/BPFunctions.h"
6
7 #include <algorithm>
8
9 #include "Common/CommonTypes.h"
10 #include "Common/Logging/Log.h"
11
12 #include "VideoCommon/AbstractFramebuffer.h"
13 #include "VideoCommon/BPMemory.h"
14 #include "VideoCommon/FramebufferManager.h"
15 #include "VideoCommon/RenderBase.h"
16 #include "VideoCommon/RenderState.h"
17 #include "VideoCommon/VertexManagerBase.h"
18 #include "VideoCommon/VideoCommon.h"
19 #include "VideoCommon/VideoConfig.h"
20 #include "VideoCommon/XFMemory.h"
21
22 namespace BPFunctions
23 {
24 // ----------------------------------------------
25 // State translation lookup tables
26 // Reference: Yet Another GameCube Documentation
27 // ----------------------------------------------
28
FlushPipeline()29 void FlushPipeline()
30 {
31 g_vertex_manager->Flush();
32 }
33
SetGenerationMode()34 void SetGenerationMode()
35 {
36 g_vertex_manager->SetRasterizationStateChanged();
37 }
38
SetScissor()39 void SetScissor()
40 {
41 /* NOTE: the minimum value here for the scissor rect and offset is -342.
42 * GX internally adds on an offset of 342 to both the offset and scissor
43 * coords to ensure that the register was always unsigned.
44 *
45 * The code that was here before tried to "undo" this offset, but
46 * since we always take the difference, the +342 added to both
47 * sides cancels out. */
48
49 /* The scissor offset is always even, so to save space, the scissor offset
50 * register is scaled down by 2. So, if somebody calls
51 * GX_SetScissorBoxOffset(20, 20); the registers will be set to 10, 10. */
52 const int xoff = bpmem.scissorOffset.x * 2;
53 const int yoff = bpmem.scissorOffset.y * 2;
54
55 MathUtil::Rectangle<int> native_rc(bpmem.scissorTL.x - xoff, bpmem.scissorTL.y - yoff,
56 bpmem.scissorBR.x - xoff + 1, bpmem.scissorBR.y - yoff + 1);
57 native_rc.ClampUL(0, 0, EFB_WIDTH, EFB_HEIGHT);
58
59 auto target_rc = g_renderer->ConvertEFBRectangle(native_rc);
60 auto converted_rc =
61 g_renderer->ConvertFramebufferRectangle(target_rc, g_renderer->GetCurrentFramebuffer());
62 g_renderer->SetScissorRect(converted_rc);
63 }
64
SetViewport()65 void SetViewport()
66 {
67 int scissor_x_off = bpmem.scissorOffset.x * 2;
68 int scissor_y_off = bpmem.scissorOffset.y * 2;
69 float x = g_renderer->EFBToScaledXf(xfmem.viewport.xOrig - xfmem.viewport.wd - scissor_x_off);
70 float y = g_renderer->EFBToScaledYf(xfmem.viewport.yOrig + xfmem.viewport.ht - scissor_y_off);
71
72 float width = g_renderer->EFBToScaledXf(2.0f * xfmem.viewport.wd);
73 float height = g_renderer->EFBToScaledYf(-2.0f * xfmem.viewport.ht);
74 float min_depth = (xfmem.viewport.farZ - xfmem.viewport.zRange) / 16777216.0f;
75 float max_depth = xfmem.viewport.farZ / 16777216.0f;
76 if (width < 0.f)
77 {
78 x += width;
79 width *= -1;
80 }
81 if (height < 0.f)
82 {
83 y += height;
84 height *= -1;
85 }
86
87 // The maximum depth that is written to the depth buffer should never exceed this value.
88 // This is necessary because we use a 2^24 divisor for all our depth values to prevent
89 // floating-point round-trip errors. However the console GPU doesn't ever write a value
90 // to the depth buffer that exceeds 2^24 - 1.
91 constexpr float GX_MAX_DEPTH = 16777215.0f / 16777216.0f;
92 if (!g_ActiveConfig.backend_info.bSupportsDepthClamp)
93 {
94 // There's no way to support oversized depth ranges in this situation. Let's just clamp the
95 // range to the maximum value supported by the console GPU and hope for the best.
96 min_depth = std::clamp(min_depth, 0.0f, GX_MAX_DEPTH);
97 max_depth = std::clamp(max_depth, 0.0f, GX_MAX_DEPTH);
98 }
99
100 if (g_renderer->UseVertexDepthRange())
101 {
102 // We need to ensure depth values are clamped the maximum value supported by the console GPU.
103 // Taking into account whether the depth range is inverted or not.
104 if (xfmem.viewport.zRange < 0.0f && g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
105 {
106 min_depth = GX_MAX_DEPTH;
107 max_depth = 0.0f;
108 }
109 else
110 {
111 min_depth = 0.0f;
112 max_depth = GX_MAX_DEPTH;
113 }
114 }
115
116 float near_depth, far_depth;
117 if (g_ActiveConfig.backend_info.bSupportsReversedDepthRange)
118 {
119 // Set the reversed depth range.
120 near_depth = max_depth;
121 far_depth = min_depth;
122 }
123 else
124 {
125 // We use an inverted depth range here to apply the Reverse Z trick.
126 // This trick makes sure we match the precision provided by the 1:0
127 // clipping depth range on the hardware.
128 near_depth = 1.0f - max_depth;
129 far_depth = 1.0f - min_depth;
130 }
131
132 // Clamp to size if oversized not supported. Required for D3D.
133 if (!g_ActiveConfig.backend_info.bSupportsOversizedViewports)
134 {
135 const float max_width = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetWidth());
136 const float max_height = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetHeight());
137 x = std::clamp(x, 0.0f, max_width - 1.0f);
138 y = std::clamp(y, 0.0f, max_height - 1.0f);
139 width = std::clamp(width, 1.0f, max_width - x);
140 height = std::clamp(height, 1.0f, max_height - y);
141 }
142
143 // Lower-left flip.
144 if (g_ActiveConfig.backend_info.bUsesLowerLeftOrigin)
145 y = static_cast<float>(g_renderer->GetCurrentFramebuffer()->GetHeight()) - y - height;
146
147 g_renderer->SetViewport(x, y, width, height, near_depth, far_depth);
148 }
149
SetDepthMode()150 void SetDepthMode()
151 {
152 g_vertex_manager->SetDepthStateChanged();
153 }
154
SetBlendMode()155 void SetBlendMode()
156 {
157 g_vertex_manager->SetBlendingStateChanged();
158 }
159
160 /* Explanation of the magic behind ClearScreen:
161 There's numerous possible formats for the pixel data in the EFB.
162 However, in the HW accelerated backends we're always using RGBA8
163 for the EFB format, which causes some problems:
164 - We're using an alpha channel although the game doesn't
165 - If the actual EFB format is RGBA6_Z24 or R5G6B5_Z16, we are using more bits per channel than the
166 native HW
167
168 To properly emulate the above points, we're doing the following:
169 (1)
170 - disable alpha channel writing of any kind of rendering if the actual EFB format doesn't use an
171 alpha channel
172 - NOTE: Always make sure that the EFB has been cleared to an alpha value of 0xFF in this case!
173 - Same for color channels, these need to be cleared to 0x00 though.
174 (2)
175 - convert the RGBA8 color to RGBA6/RGB8/RGB565 and convert it to RGBA8 again
176 - convert the Z24 depth value to Z16 and back to Z24
177 */
ClearScreen(const MathUtil::Rectangle<int> & rc)178 void ClearScreen(const MathUtil::Rectangle<int>& rc)
179 {
180 bool colorEnable = (bpmem.blendmode.colorupdate != 0);
181 bool alphaEnable = (bpmem.blendmode.alphaupdate != 0);
182 bool zEnable = (bpmem.zmode.updateenable != 0);
183 auto pixel_format = bpmem.zcontrol.pixel_format;
184
185 // (1): Disable unused color channels
186 if (pixel_format == PEControl::RGB8_Z24 || pixel_format == PEControl::RGB565_Z16 ||
187 pixel_format == PEControl::Z24)
188 {
189 alphaEnable = false;
190 }
191
192 if (colorEnable || alphaEnable || zEnable)
193 {
194 u32 color = (bpmem.clearcolorAR << 16) | bpmem.clearcolorGB;
195 u32 z = bpmem.clearZValue;
196
197 // (2) drop additional accuracy
198 if (pixel_format == PEControl::RGBA6_Z24)
199 {
200 color = RGBA8ToRGBA6ToRGBA8(color);
201 }
202 else if (pixel_format == PEControl::RGB565_Z16)
203 {
204 color = RGBA8ToRGB565ToRGBA8(color);
205 z = Z24ToZ16ToZ24(z);
206 }
207 g_renderer->ClearScreen(rc, colorEnable, alphaEnable, zEnable, color, z);
208 }
209 }
210
OnPixelFormatChange()211 void OnPixelFormatChange()
212 {
213 // TODO : Check for Z compression format change
214 // When using 16bit Z, the game may enable a special compression format which we need to handle
215 // If we don't, Z values will be completely screwed up, currently only Star Wars:RS2 uses that.
216
217 /*
218 * When changing the EFB format, the pixel data won't get converted to the new format but stays
219 * the same.
220 * Since we are always using an RGBA8 buffer though, this causes issues in some games.
221 * Thus, we reinterpret the old EFB data with the new format here.
222 */
223 if (!g_ActiveConfig.bEFBEmulateFormatChanges)
224 return;
225
226 auto old_format = g_renderer->GetPrevPixelFormat();
227 auto new_format = bpmem.zcontrol.pixel_format;
228 g_renderer->StorePixelFormat(new_format);
229
230 DEBUG_LOG(VIDEO, "pixelfmt: pixel=%d, zc=%d", static_cast<int>(new_format),
231 static_cast<int>(bpmem.zcontrol.zformat));
232
233 // no need to reinterpret pixel data in these cases
234 if (new_format == old_format || old_format == PEControl::INVALID_FMT)
235 return;
236
237 // Check for pixel format changes
238 switch (old_format)
239 {
240 case PEControl::RGB8_Z24:
241 case PEControl::Z24:
242 {
243 // Z24 and RGB8_Z24 are treated equal, so just return in this case
244 if (new_format == PEControl::RGB8_Z24 || new_format == PEControl::Z24)
245 return;
246
247 if (new_format == PEControl::RGBA6_Z24)
248 {
249 g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB8ToRGBA6);
250 return;
251 }
252 else if (new_format == PEControl::RGB565_Z16)
253 {
254 g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB8ToRGB565);
255 return;
256 }
257 }
258 break;
259
260 case PEControl::RGBA6_Z24:
261 {
262 if (new_format == PEControl::RGB8_Z24 || new_format == PEControl::Z24)
263 {
264 g_renderer->ReinterpretPixelData(EFBReinterpretType::RGBA6ToRGB8);
265 return;
266 }
267 else if (new_format == PEControl::RGB565_Z16)
268 {
269 g_renderer->ReinterpretPixelData(EFBReinterpretType::RGBA6ToRGB565);
270 return;
271 }
272 }
273 break;
274
275 case PEControl::RGB565_Z16:
276 {
277 if (new_format == PEControl::RGB8_Z24 || new_format == PEControl::Z24)
278 {
279 g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB565ToRGB8);
280 return;
281 }
282 else if (new_format == PEControl::RGBA6_Z24)
283 {
284 g_renderer->ReinterpretPixelData(EFBReinterpretType::RGB565ToRGBA6);
285 return;
286 }
287 }
288 break;
289
290 default:
291 break;
292 }
293
294 ERROR_LOG(VIDEO, "Unhandled EFB format change: %d to %d", static_cast<int>(old_format),
295 static_cast<int>(new_format));
296 }
297
SetInterlacingMode(const BPCmd & bp)298 void SetInterlacingMode(const BPCmd& bp)
299 {
300 // TODO
301 switch (bp.address)
302 {
303 case BPMEM_FIELDMODE:
304 {
305 // SDK always sets bpmem.lineptwidth.lineaspect via BPMEM_LINEPTWIDTH
306 // just before this cmd
307 const char* action[] = {"don't adjust", "adjust"};
308 DEBUG_LOG(VIDEO, "BPMEM_FIELDMODE texLOD:%s lineaspect:%s", action[bpmem.fieldmode.texLOD],
309 action[bpmem.lineptwidth.lineaspect]);
310 }
311 break;
312 case BPMEM_FIELDMASK:
313 {
314 // Determines if fields will be written to EFB (always computed)
315 const char* action[] = {"skip", "write"};
316 DEBUG_LOG(VIDEO, "BPMEM_FIELDMASK even:%s odd:%s", action[bpmem.fieldmask.even],
317 action[bpmem.fieldmask.odd]);
318 }
319 break;
320 default:
321 ERROR_LOG(VIDEO, "SetInterlacingMode default");
322 break;
323 }
324 }
325 }; // namespace BPFunctions
326