123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- // Copyright 2009 Dolphin Emulator Project
- // SPDX-License-Identifier: GPL-2.0-or-later
- /*
- Portions of this file are based off work by Markus Trenkwalder.
- Copyright (c) 2007, 2008 Markus Trenkwalder
- All rights reserved.
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
- * Neither the name of the library's copyright owner nor the names of its
- contributors may be used to endorse or promote products derived from this
- software without specific prior written permission.
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
- PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
- PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- #include "VideoBackends/Software/Clipper.h"
- #include "Common/Assert.h"
- #include "VideoBackends/Software/NativeVertexFormat.h"
- #include "VideoBackends/Software/Rasterizer.h"
- #include "VideoCommon/BPMemory.h"
- #include "VideoCommon/Statistics.h"
- #include "VideoCommon/XFMemory.h"
- namespace Clipper
- {
- enum
- {
- NUM_CLIPPED_VERTICES = 33,
- NUM_INDICES = NUM_CLIPPED_VERTICES + 3
- };
- static OutputVertexData ClippedVertices[NUM_CLIPPED_VERTICES];
- static OutputVertexData* Vertices[NUM_INDICES];
- void Init()
- {
- for (int i = 0; i < NUM_CLIPPED_VERTICES; ++i)
- Vertices[i + 3] = &ClippedVertices[i];
- }
- enum
- {
- SKIP_FLAG = -1,
- CLIP_POS_X_BIT = 0x01,
- CLIP_NEG_X_BIT = 0x02,
- CLIP_POS_Y_BIT = 0x04,
- CLIP_NEG_Y_BIT = 0x08,
- CLIP_POS_Z_BIT = 0x10,
- CLIP_NEG_Z_BIT = 0x20
- };
- static inline int CalcClipMask(const OutputVertexData* v)
- {
- int cmask = 0;
- Vec4 pos = v->projectedPosition;
- if (pos.w - pos.x < 0)
- cmask |= CLIP_POS_X_BIT;
- if (pos.x + pos.w < 0)
- cmask |= CLIP_NEG_X_BIT;
- if (pos.w - pos.y < 0)
- cmask |= CLIP_POS_Y_BIT;
- if (pos.y + pos.w < 0)
- cmask |= CLIP_NEG_Y_BIT;
- if (pos.w * pos.z > 0)
- cmask |= CLIP_POS_Z_BIT;
- if (pos.z + pos.w < 0)
- cmask |= CLIP_NEG_Z_BIT;
- return cmask;
- }
- static inline void AddInterpolatedVertex(float t, int out, int in, int* numVertices)
- {
- Vertices[(*numVertices)++]->Lerp(t, Vertices[out], Vertices[in]);
- }
- #define DIFFERENT_SIGNS(x, y) ((x <= 0 && y > 0) || (x > 0 && y <= 0))
- #define CLIP_DOTPROD(I, A, B, C, D) \
- (Vertices[I]->projectedPosition.x * A + Vertices[I]->projectedPosition.y * B + \
- Vertices[I]->projectedPosition.z * C + Vertices[I]->projectedPosition.w * D)
- #define POLY_CLIP(PLANE_BIT, A, B, C, D) \
- { \
- if (mask & PLANE_BIT) \
- { \
- int idxPrev = inlist[0]; \
- float dpPrev = CLIP_DOTPROD(idxPrev, A, B, C, D); \
- int outcount = 0; \
- \
- inlist[n] = inlist[0]; \
- for (int j = 1; j <= n; j++) \
- { \
- int idx = inlist[j]; \
- float dp = CLIP_DOTPROD(idx, A, B, C, D); \
- if (dpPrev >= 0) \
- { \
- outlist[outcount++] = idxPrev; \
- } \
- \
- if (DIFFERENT_SIGNS(dp, dpPrev)) \
- { \
- if (dp < 0) \
- { \
- float t = dp / (dp - dpPrev); \
- AddInterpolatedVertex(t, idx, idxPrev, &numVertices); \
- } \
- else \
- { \
- float t = dpPrev / (dpPrev - dp); \
- AddInterpolatedVertex(t, idxPrev, idx, &numVertices); \
- } \
- outlist[outcount++] = numVertices - 1; \
- } \
- \
- idxPrev = idx; \
- dpPrev = dp; \
- } \
- \
- if (outcount < 3) \
- continue; \
- \
- { \
- int* tmp = inlist; \
- inlist = outlist; \
- outlist = tmp; \
- n = outcount; \
- } \
- } \
- }
- #define LINE_CLIP(PLANE_BIT, A, B, C, D) \
- { \
- if (mask & PLANE_BIT) \
- { \
- const float dp0 = CLIP_DOTPROD(0, A, B, C, D); \
- const float dp1 = CLIP_DOTPROD(1, A, B, C, D); \
- const bool neg_dp0 = dp0 < 0; \
- const bool neg_dp1 = dp1 < 0; \
- \
- if (neg_dp0 && neg_dp1) \
- return; \
- \
- if (neg_dp1) \
- { \
- float t = dp1 / (dp1 - dp0); \
- if (t > t1) \
- t1 = t; \
- } \
- else if (neg_dp0) \
- { \
- float t = dp0 / (dp0 - dp1); \
- if (t > t0) \
- t0 = t; \
- } \
- } \
- }
- static void ClipTriangle(int* indices, int* numIndices)
- {
- int mask = 0;
- mask |= CalcClipMask(Vertices[0]);
- mask |= CalcClipMask(Vertices[1]);
- mask |= CalcClipMask(Vertices[2]);
- if (mask != 0)
- {
- for (int i = 0; i < 3; i += 3)
- {
- int vlist[2][2 * 6 + 1];
- int *inlist = vlist[0], *outlist = vlist[1];
- int n = 3;
- int numVertices = 3;
- inlist[0] = 0;
- inlist[1] = 1;
- inlist[2] = 2;
- // mark this triangle as unused in case it should be completely
- // clipped
- indices[0] = SKIP_FLAG;
- indices[1] = SKIP_FLAG;
- indices[2] = SKIP_FLAG;
- POLY_CLIP(CLIP_POS_X_BIT, -1, 0, 0, 1);
- POLY_CLIP(CLIP_NEG_X_BIT, 1, 0, 0, 1);
- POLY_CLIP(CLIP_POS_Y_BIT, 0, -1, 0, 1);
- POLY_CLIP(CLIP_NEG_Y_BIT, 0, 1, 0, 1);
- POLY_CLIP(CLIP_POS_Z_BIT, 0, 0, 0, 1);
- POLY_CLIP(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
- INCSTAT(g_stats.this_frame.num_triangles_clipped);
- // transform the poly in inlist into triangles
- indices[0] = inlist[0];
- indices[1] = inlist[1];
- indices[2] = inlist[2];
- for (int j = 3; j < n; ++j)
- {
- indices[(*numIndices)++] = inlist[0];
- indices[(*numIndices)++] = inlist[j - 1];
- indices[(*numIndices)++] = inlist[j];
- }
- }
- }
- }
- static void ClipLine(int* indices)
- {
- int mask = 0;
- int clip_mask[2] = {0, 0};
- for (int i = 0; i < 2; ++i)
- {
- clip_mask[i] = CalcClipMask(Vertices[i]);
- mask |= clip_mask[i];
- }
- if (mask == 0)
- return;
- float t0 = 0;
- float t1 = 0;
- // Mark unused in case of early termination
- // of the macros below. (When fully clipped)
- indices[0] = SKIP_FLAG;
- indices[1] = SKIP_FLAG;
- LINE_CLIP(CLIP_POS_X_BIT, -1, 0, 0, 1);
- LINE_CLIP(CLIP_NEG_X_BIT, 1, 0, 0, 1);
- LINE_CLIP(CLIP_POS_Y_BIT, 0, -1, 0, 1);
- LINE_CLIP(CLIP_NEG_Y_BIT, 0, 1, 0, 1);
- LINE_CLIP(CLIP_POS_Z_BIT, 0, 0, -1, 1);
- LINE_CLIP(CLIP_NEG_Z_BIT, 0, 0, 1, 1);
- // Restore the old values as this line
- // was not fully clipped.
- indices[0] = 0;
- indices[1] = 1;
- int numVertices = 2;
- if (clip_mask[0])
- {
- indices[0] = numVertices;
- AddInterpolatedVertex(t0, 0, 1, &numVertices);
- }
- if (clip_mask[1])
- {
- indices[1] = numVertices;
- AddInterpolatedVertex(t1, 1, 0, &numVertices);
- }
- }
- void ProcessTriangle(OutputVertexData* v0, OutputVertexData* v1, OutputVertexData* v2)
- {
- INCSTAT(g_stats.this_frame.num_triangles_in);
- if (IsTriviallyRejected(v0, v1, v2))
- {
- INCSTAT(g_stats.this_frame.num_triangles_rejected);
- // NOTE: The slope used by zfreeze shouldn't be updated if the triangle is
- // trivially rejected during clipping
- return;
- }
- bool backface = IsBackface(v0, v1, v2);
- if (!backface)
- {
- if (bpmem.genMode.cullmode == CullMode::Back || bpmem.genMode.cullmode == CullMode::All)
- {
- // cull frontfacing - we still need to update the slope for zfreeze
- PerspectiveDivide(v0);
- PerspectiveDivide(v1);
- PerspectiveDivide(v2);
- Rasterizer::UpdateZSlope(v0, v1, v2, bpmem.scissorOffset.x * 2, bpmem.scissorOffset.y * 2);
- INCSTAT(g_stats.this_frame.num_triangles_culled);
- return;
- }
- }
- else
- {
- if (bpmem.genMode.cullmode == CullMode::Front || bpmem.genMode.cullmode == CullMode::All)
- {
- // cull backfacing - we still need to update the slope for zfreeze
- PerspectiveDivide(v0);
- PerspectiveDivide(v2);
- PerspectiveDivide(v1);
- Rasterizer::UpdateZSlope(v0, v2, v1, bpmem.scissorOffset.x * 2, bpmem.scissorOffset.y * 2);
- INCSTAT(g_stats.this_frame.num_triangles_culled);
- return;
- }
- }
- int indices[NUM_INDICES] = {0, 1, 2, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG,
- SKIP_FLAG, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG,
- SKIP_FLAG, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG, SKIP_FLAG,
- SKIP_FLAG, SKIP_FLAG, SKIP_FLAG};
- int numIndices = 3;
- if (backface)
- {
- Vertices[0] = v0;
- Vertices[1] = v2;
- Vertices[2] = v1;
- }
- else
- {
- Vertices[0] = v0;
- Vertices[1] = v1;
- Vertices[2] = v2;
- }
- // TODO: behavior when disable_clipping_detection is set doesn't quite match actual hardware;
- // there does still seem to be a maximum size after which things are clipped. Also, currently
- // when clipping is enabled triangles are clipped to exactly the viewport, but on hardware there
- // is a guardband (and with certain scissor configurations, things can show up in it)
- // Mario Party 8 in widescreen breaks without this: https://bugs.dolphin-emu.org/issues/12769
- bool skip_clipping = false;
- if (xfmem.clipDisable.disable_clipping_detection)
- {
- // If any w coordinate is negative, then the perspective divide will flip coordinates, breaking
- // various assumptions (including backface). So, we still need to do clipping in that case.
- // This isn't the actual condition hardware uses.
- if (Vertices[0]->projectedPosition.w >= 0 && Vertices[1]->projectedPosition.w >= 0 &&
- Vertices[2]->projectedPosition.w >= 0)
- skip_clipping = true;
- }
- if (!skip_clipping)
- ClipTriangle(indices, &numIndices);
- for (int i = 0; i + 3 <= numIndices; i += 3)
- {
- ASSERT(i < NUM_INDICES);
- if (indices[i] != SKIP_FLAG)
- {
- PerspectiveDivide(Vertices[indices[i]]);
- PerspectiveDivide(Vertices[indices[i + 1]]);
- PerspectiveDivide(Vertices[indices[i + 2]]);
- Rasterizer::DrawTriangleFrontFace(Vertices[indices[i]], Vertices[indices[i + 1]],
- Vertices[indices[i + 2]]);
- }
- }
- }
- constexpr std::array<float, 8> LINE_PT_TEX_OFFSETS = {
- 0, 1 / 16.f, 1 / 8.f, 1 / 4.f, 1 / 2.f, 1, 1, 1,
- };
- static void CopyLineVertex(OutputVertexData* dst, const OutputVertexData* src, int px, int py,
- bool apply_line_offset)
- {
- const float line_half_width = bpmem.lineptwidth.linesize / 12.0f;
- dst->projectedPosition = src->projectedPosition;
- dst->screenPosition.x = src->screenPosition.x + px * line_half_width;
- dst->screenPosition.y = src->screenPosition.y + py * line_half_width;
- dst->screenPosition.z = src->screenPosition.z;
- dst->normal = src->normal;
- dst->color = src->color;
- dst->texCoords = src->texCoords;
- if (apply_line_offset && LINE_PT_TEX_OFFSETS[bpmem.lineptwidth.lineoff] != 0)
- {
- for (u32 coord_num = 0; coord_num < xfmem.numTexGen.numTexGens; coord_num++)
- {
- if (bpmem.texcoords[coord_num].s.line_offset)
- {
- dst->texCoords[coord_num].x += (bpmem.texcoords[coord_num].s.scale_minus_1 + 1) *
- LINE_PT_TEX_OFFSETS[bpmem.lineptwidth.lineoff];
- }
- }
- }
- }
- void ProcessLine(OutputVertexData* lineV0, OutputVertexData* lineV1)
- {
- int indices[4] = {0, 1, SKIP_FLAG, SKIP_FLAG};
- Vertices[0] = lineV0;
- Vertices[1] = lineV1;
- // point to a valid vertex to store to when clipping
- Vertices[2] = &ClippedVertices[17];
- ClipLine(indices);
- if (indices[0] != SKIP_FLAG)
- {
- OutputVertexData* v0 = Vertices[indices[0]];
- OutputVertexData* v1 = Vertices[indices[1]];
- PerspectiveDivide(v0);
- PerspectiveDivide(v1);
- const float dx = v1->screenPosition.x - v0->screenPosition.x;
- const float dy = v1->screenPosition.y - v0->screenPosition.y;
- int px = 0;
- int py = 0;
- // GameCube/Wii's line drawing algorithm is a little quirky. It does not
- // use the correct line caps. Instead, the line caps are vertical or
- // horizontal depending the slope of the line.
- // FIXME: What does real hardware do when line is at a 45-degree angle?
- // Note that py or px are set positive or negative to ensure that the triangles are drawn ccw.
- if (fabsf(dx) > fabsf(dy))
- py = (dx > 0) ? -1 : 1;
- else
- px = (dy > 0) ? 1 : -1;
- OutputVertexData triangle[3];
- CopyLineVertex(&triangle[0], v0, px, py, false);
- CopyLineVertex(&triangle[1], v1, px, py, false);
- CopyLineVertex(&triangle[2], v1, -px, -py, true);
- // ccw winding
- Rasterizer::DrawTriangleFrontFace(&triangle[2], &triangle[1], &triangle[0]);
- CopyLineVertex(&triangle[1], v0, -px, -py, true);
- Rasterizer::DrawTriangleFrontFace(&triangle[0], &triangle[1], &triangle[2]);
- }
- }
- static void CopyPointVertex(OutputVertexData* dst, const OutputVertexData* src, bool px, bool py)
- {
- const float point_radius = bpmem.lineptwidth.pointsize / 12.0f;
- dst->projectedPosition = src->projectedPosition;
- dst->screenPosition.x = src->screenPosition.x + (px ? 1 : -1) * point_radius;
- dst->screenPosition.y = src->screenPosition.y + (py ? 1 : -1) * point_radius;
- dst->screenPosition.z = src->screenPosition.z;
- dst->normal = src->normal;
- dst->color = src->color;
- dst->texCoords = src->texCoords;
- const float point_offset = LINE_PT_TEX_OFFSETS[bpmem.lineptwidth.pointoff];
- if (point_offset != 0)
- {
- for (u32 coord_num = 0; coord_num < xfmem.numTexGen.numTexGens; coord_num++)
- {
- const auto coord_info = bpmem.texcoords[coord_num];
- if (coord_info.s.point_offset)
- {
- if (px)
- dst->texCoords[coord_num].x += (coord_info.s.scale_minus_1 + 1) * point_offset;
- if (py)
- dst->texCoords[coord_num].y += (coord_info.t.scale_minus_1 + 1) * point_offset;
- }
- }
- }
- }
- void ProcessPoint(OutputVertexData* center)
- {
- // TODO: This isn't actually doing any clipping
- PerspectiveDivide(center);
- OutputVertexData ll, lr, ul, ur;
- CopyPointVertex(&ll, center, false, false);
- CopyPointVertex(&lr, center, true, false);
- CopyPointVertex(&ur, center, true, true);
- CopyPointVertex(&ul, center, false, true);
- Rasterizer::DrawTriangleFrontFace(&ll, &ul, &lr);
- Rasterizer::DrawTriangleFrontFace(&ur, &lr, &ul);
- }
- bool IsTriviallyRejected(const OutputVertexData* v0, const OutputVertexData* v1,
- const OutputVertexData* v2)
- {
- int mask = CalcClipMask(v0);
- mask &= CalcClipMask(v1);
- mask &= CalcClipMask(v2);
- return mask != 0;
- }
- bool IsBackface(const OutputVertexData* v0, const OutputVertexData* v1, const OutputVertexData* v2)
- {
- float x0 = v0->projectedPosition.x;
- float x1 = v1->projectedPosition.x;
- float x2 = v2->projectedPosition.x;
- float y1 = v1->projectedPosition.y;
- float y0 = v0->projectedPosition.y;
- float y2 = v2->projectedPosition.y;
- float w0 = v0->projectedPosition.w;
- float w1 = v1->projectedPosition.w;
- float w2 = v2->projectedPosition.w;
- float normalZDir = (x0 * w2 - x2 * w0) * y1 + (x2 * y0 - x0 * y2) * w1 + (y2 * w0 - y0 * w2) * x1;
- bool backface = normalZDir <= 0.0f;
- // Jimmie Johnson's Anything with an Engine has a positive viewport, while other games have a
- // negative viewport. The positive viewport does not require vertices to be vertically mirrored,
- // but the backface test does need to be inverted for things to be drawn.
- if (xfmem.viewport.ht > 0)
- backface = !backface;
- return backface;
- }
- void PerspectiveDivide(OutputVertexData* vertex)
- {
- Vec4& projected = vertex->projectedPosition;
- Vec3& screen = vertex->screenPosition;
- float wInverse = 1.0f / projected.w;
- screen.x = projected.x * wInverse * xfmem.viewport.wd + xfmem.viewport.xOrig;
- screen.y = projected.y * wInverse * xfmem.viewport.ht + xfmem.viewport.yOrig;
- screen.z = projected.z * wInverse * xfmem.viewport.zRange + xfmem.viewport.farZ;
- }
- } // namespace Clipper
|