diff options
| author | 2014-07-27 18:02:35 +0200 | |
|---|---|---|
| committer | 2014-08-12 13:50:07 +0200 | |
| commit | 94d742fe172ba933af321bfb0e02889b40d0c179 (patch) | |
| tree | 241e6d8b36e6ab9921ef7afb71e7350e52862e2a /src/video_core/rasterizer.cpp | |
| parent | Pica: Add triangle clipper. (diff) | |
| download | yuzu-94d742fe172ba933af321bfb0e02889b40d0c179.tar.gz yuzu-94d742fe172ba933af321bfb0e02889b40d0c179.tar.xz yuzu-94d742fe172ba933af321bfb0e02889b40d0c179.zip | |
Pica: Add basic rasterizer.
Diffstat (limited to 'src/video_core/rasterizer.cpp')
| -rw-r--r-- | src/video_core/rasterizer.cpp | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp new file mode 100644 index 000000000..a7c1bab3e --- /dev/null +++ b/src/video_core/rasterizer.cpp | |||
| @@ -0,0 +1,180 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <algorithm> | ||
| 6 | |||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | #include "math.h" | ||
| 10 | #include "pica.h" | ||
| 11 | #include "rasterizer.h" | ||
| 12 | #include "vertex_shader.h" | ||
| 13 | |||
| 14 | namespace Pica { | ||
| 15 | |||
| 16 | namespace Rasterizer { | ||
| 17 | |||
| 18 | static void DrawPixel(int x, int y, const Math::Vec4<u8>& color) { | ||
| 19 | u32* color_buffer = (u32*)Memory::GetPointer(registers.framebuffer.GetColorBufferAddress()); | ||
| 20 | u32 value = (color.a() << 24) | (color.r() << 16) | (color.g() << 8) | color.b(); | ||
| 21 | |||
| 22 | // Assuming RGBA8 format until actual framebuffer format handling is implemented | ||
| 23 | *(color_buffer + x + y * registers.framebuffer.GetWidth() / 2) = value; | ||
| 24 | } | ||
| 25 | |||
| 26 | static u32 GetDepth(int x, int y) { | ||
| 27 | u16* depth_buffer = (u16*)Memory::GetPointer(registers.framebuffer.GetDepthBufferAddress()); | ||
| 28 | |||
| 29 | // Assuming 16-bit depth buffer format until actual format handling is implemented | ||
| 30 | return *(depth_buffer + x + y * registers.framebuffer.GetWidth() / 2); | ||
| 31 | } | ||
| 32 | |||
| 33 | static void SetDepth(int x, int y, u16 value) { | ||
| 34 | u16* depth_buffer = (u16*)Memory::GetPointer(registers.framebuffer.GetDepthBufferAddress()); | ||
| 35 | |||
| 36 | // Assuming 16-bit depth buffer format until actual format handling is implemented | ||
| 37 | *(depth_buffer + x + y * registers.framebuffer.GetWidth() / 2) = value; | ||
| 38 | } | ||
| 39 | |||
| 40 | void ProcessTriangle(const VertexShader::OutputVertex& v0, | ||
| 41 | const VertexShader::OutputVertex& v1, | ||
| 42 | const VertexShader::OutputVertex& v2) | ||
| 43 | { | ||
| 44 | // NOTE: Assuming that rasterizer coordinates are 12.4 fixed-point values | ||
| 45 | struct Fix12P4 { | ||
| 46 | Fix12P4() {} | ||
| 47 | Fix12P4(u16 val) : val(val) {} | ||
| 48 | |||
| 49 | static u16 FracMask() { return 0xF; } | ||
| 50 | static u16 IntMask() { return (u16)~0xF; } | ||
| 51 | |||
| 52 | operator u16() const { | ||
| 53 | return val; | ||
| 54 | } | ||
| 55 | |||
| 56 | bool operator < (const Fix12P4& oth) const { | ||
| 57 | return (u16)*this < (u16)oth; | ||
| 58 | } | ||
| 59 | |||
| 60 | private: | ||
| 61 | u16 val; | ||
| 62 | }; | ||
| 63 | |||
| 64 | // vertex positions in rasterizer coordinates | ||
| 65 | auto FloatToFix = [](float24 flt) { | ||
| 66 | return Fix12P4(flt.ToFloat32() * 16.0f); | ||
| 67 | }; | ||
| 68 | auto ScreenToRasterizerCoordinates = [FloatToFix](const Math::Vec3<float24> vec) { | ||
| 69 | return Math::Vec3<Fix12P4>{FloatToFix(vec.x), FloatToFix(vec.y), FloatToFix(vec.z)}; | ||
| 70 | }; | ||
| 71 | Math::Vec3<Fix12P4> vtxpos[3]{ ScreenToRasterizerCoordinates(v0.screenpos), | ||
| 72 | ScreenToRasterizerCoordinates(v1.screenpos), | ||
| 73 | ScreenToRasterizerCoordinates(v2.screenpos) }; | ||
| 74 | |||
| 75 | // TODO: Proper scissor rect test! | ||
| 76 | u16 min_x = std::min({vtxpos[0].x, vtxpos[1].x, vtxpos[2].x}); | ||
| 77 | u16 min_y = std::min({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y}); | ||
| 78 | u16 max_x = std::max({vtxpos[0].x, vtxpos[1].x, vtxpos[2].x}); | ||
| 79 | u16 max_y = std::max({vtxpos[0].y, vtxpos[1].y, vtxpos[2].y}); | ||
| 80 | |||
| 81 | min_x = min_x & Fix12P4::IntMask(); | ||
| 82 | min_y = min_y & Fix12P4::IntMask(); | ||
| 83 | max_x = (max_x + Fix12P4::FracMask()) & Fix12P4::IntMask(); | ||
| 84 | max_y = (max_y + Fix12P4::FracMask()) & Fix12P4::IntMask(); | ||
| 85 | |||
| 86 | // Triangle filling rules: Pixels on the right-sided edge or on flat bottom edges are not | ||
| 87 | // drawn. Pixels on any other triangle border are drawn. This is implemented with three bias | ||
| 88 | // values which are added to the barycentric coordinates w0, w1 and w2, respectively. | ||
| 89 | // NOTE: These are the PSP filling rules. Not sure if the 3DS uses the same ones... | ||
| 90 | auto IsRightSideOrFlatBottomEdge = [](const Math::Vec2<Fix12P4>& vtx, | ||
| 91 | const Math::Vec2<Fix12P4>& line1, | ||
| 92 | const Math::Vec2<Fix12P4>& line2) | ||
| 93 | { | ||
| 94 | if (line1.y == line2.y) { | ||
| 95 | // just check if vertex is above us => bottom line parallel to x-axis | ||
| 96 | return vtx.y < line1.y; | ||
| 97 | } else { | ||
| 98 | // check if vertex is on our left => right side | ||
| 99 | // TODO: Not sure how likely this is to overflow | ||
| 100 | return (int)vtx.x < (int)line1.x + ((int)line2.x - (int)line1.x) * ((int)vtx.y - (int)line1.y) / ((int)line2.y - (int)line1.y); | ||
| 101 | } | ||
| 102 | }; | ||
| 103 | int bias0 = IsRightSideOrFlatBottomEdge(vtxpos[0].xy(), vtxpos[1].xy(), vtxpos[2].xy()) ? -1 : 0; | ||
| 104 | int bias1 = IsRightSideOrFlatBottomEdge(vtxpos[1].xy(), vtxpos[2].xy(), vtxpos[0].xy()) ? -1 : 0; | ||
| 105 | int bias2 = IsRightSideOrFlatBottomEdge(vtxpos[2].xy(), vtxpos[0].xy(), vtxpos[1].xy()) ? -1 : 0; | ||
| 106 | |||
| 107 | // TODO: Not sure if looping through x first might be faster | ||
| 108 | for (u16 y = min_y; y < max_y; y += 0x10) { | ||
| 109 | for (u16 x = min_x; x < max_x; x += 0x10) { | ||
| 110 | |||
| 111 | // Calculate the barycentric coordinates w0, w1 and w2 | ||
| 112 | auto orient2d = [](const Math::Vec2<Fix12P4>& vtx1, | ||
| 113 | const Math::Vec2<Fix12P4>& vtx2, | ||
| 114 | const Math::Vec2<Fix12P4>& vtx3) { | ||
| 115 | const auto vec1 = (vtx2.Cast<int>() - vtx1.Cast<int>()).Append(0); | ||
| 116 | const auto vec2 = (vtx3.Cast<int>() - vtx1.Cast<int>()).Append(0); | ||
| 117 | // TODO: There is a very small chance this will overflow for sizeof(int) == 4 | ||
| 118 | return Cross(vec1, vec2).z; | ||
| 119 | }; | ||
| 120 | |||
| 121 | int w0 = bias0 + orient2d(vtxpos[1].xy(), vtxpos[2].xy(), {x, y}); | ||
| 122 | int w1 = bias1 + orient2d(vtxpos[2].xy(), vtxpos[0].xy(), {x, y}); | ||
| 123 | int w2 = bias2 + orient2d(vtxpos[0].xy(), vtxpos[1].xy(), {x, y}); | ||
| 124 | int wsum = w0 + w1 + w2; | ||
| 125 | |||
| 126 | // If current pixel is not covered by the current primitive | ||
| 127 | if (w0 < 0 || w1 < 0 || w2 < 0) | ||
| 128 | continue; | ||
| 129 | |||
| 130 | // Perspective correct attribute interpolation: | ||
| 131 | // Attribute values cannot be calculated by simple linear interpolation since | ||
| 132 | // they are not linear in screen space. For example, when interpolating a | ||
| 133 | // texture coordinate across two vertices, something simple like | ||
| 134 | // u = (u0*w0 + u1*w1)/(w0+w1) | ||
| 135 | // will not work. However, the attribute value divided by the | ||
| 136 | // clipspace w-coordinate (u/w) and and the inverse w-coordinate (1/w) are linear | ||
| 137 | // in screenspace. Hence, we can linearly interpolate these two independently and | ||
| 138 | // calculate the interpolated attribute by dividing the results. | ||
| 139 | // I.e. | ||
| 140 | // u_over_w = ((u0/v0.pos.w)*w0 + (u1/v1.pos.w)*w1)/(w0+w1) | ||
| 141 | // one_over_w = (( 1/v0.pos.w)*w0 + ( 1/v1.pos.w)*w1)/(w0+w1) | ||
| 142 | // u = u_over_w / one_over_w | ||
| 143 | // | ||
| 144 | // The generalization to three vertices is straightforward in baricentric coordinates. | ||
| 145 | auto GetInterpolatedAttribute = [&](float24 attr0, float24 attr1, float24 attr2) { | ||
| 146 | auto attr_over_w = Math::MakeVec3(attr0 / v0.pos.w, | ||
| 147 | attr1 / v1.pos.w, | ||
| 148 | attr2 / v2.pos.w); | ||
| 149 | auto w_inverse = Math::MakeVec3(float24::FromFloat32(1.f) / v0.pos.w, | ||
| 150 | float24::FromFloat32(1.f) / v1.pos.w, | ||
| 151 | float24::FromFloat32(1.f) / v2.pos.w); | ||
| 152 | auto baricentric_coordinates = Math::MakeVec3(float24::FromFloat32(w0), | ||
| 153 | float24::FromFloat32(w1), | ||
| 154 | float24::FromFloat32(w2)); | ||
| 155 | |||
| 156 | float24 interpolated_attr_over_w = Math::Dot(attr_over_w, baricentric_coordinates); | ||
| 157 | float24 interpolated_w_inverse = Math::Dot(w_inverse, baricentric_coordinates); | ||
| 158 | return interpolated_attr_over_w / interpolated_w_inverse; | ||
| 159 | }; | ||
| 160 | |||
| 161 | Math::Vec4<u8> primary_color{ | ||
| 162 | (u8)(GetInterpolatedAttribute(v0.color.r(), v1.color.r(), v2.color.r()).ToFloat32() * 255), | ||
| 163 | (u8)(GetInterpolatedAttribute(v0.color.g(), v1.color.g(), v2.color.g()).ToFloat32() * 255), | ||
| 164 | (u8)(GetInterpolatedAttribute(v0.color.b(), v1.color.b(), v2.color.b()).ToFloat32() * 255), | ||
| 165 | (u8)(GetInterpolatedAttribute(v0.color.a(), v1.color.a(), v2.color.a()).ToFloat32() * 255) | ||
| 166 | }; | ||
| 167 | |||
| 168 | u16 z = (u16)(((float)v0.screenpos[2].ToFloat32() * w0 + | ||
| 169 | (float)v1.screenpos[2].ToFloat32() * w1 + | ||
| 170 | (float)v2.screenpos[2].ToFloat32() * w2) * 65535.f / wsum); // TODO: Shouldn't need to multiply by 65536? | ||
| 171 | SetDepth(x >> 4, y >> 4, z); | ||
| 172 | |||
| 173 | DrawPixel(x >> 4, y >> 4, primary_color); | ||
| 174 | } | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | } // namespace Rasterizer | ||
| 179 | |||
| 180 | } // namespace Pica | ||