clipper.cpp 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. // Copyright 2014 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <boost/container/static_vector.hpp>
  5. #include "clipper.h"
  6. #include "pica.h"
  7. #include "rasterizer.h"
  8. #include "vertex_shader.h"
  9. namespace Pica {
  10. namespace Clipper {
  11. struct ClippingEdge {
  12. public:
  13. enum Type {
  14. POS_X = 0,
  15. NEG_X = 1,
  16. POS_Y = 2,
  17. NEG_Y = 3,
  18. POS_Z = 4,
  19. NEG_Z = 5,
  20. };
  21. ClippingEdge(Type type, float24 position) : type(type), pos(position) {}
  22. bool IsInside(const OutputVertex& vertex) const {
  23. switch (type) {
  24. case POS_X: return vertex.pos.x <= pos * vertex.pos.w;
  25. case NEG_X: return vertex.pos.x >= pos * vertex.pos.w;
  26. case POS_Y: return vertex.pos.y <= pos * vertex.pos.w;
  27. case NEG_Y: return vertex.pos.y >= pos * vertex.pos.w;
  28. // TODO: Check z compares ... should be 0..1 instead?
  29. case POS_Z: return vertex.pos.z <= pos * vertex.pos.w;
  30. default:
  31. case NEG_Z: return vertex.pos.z >= pos * vertex.pos.w;
  32. }
  33. }
  34. bool IsOutSide(const OutputVertex& vertex) const {
  35. return !IsInside(vertex);
  36. }
  37. OutputVertex GetIntersection(const OutputVertex& v0, const OutputVertex& v1) const {
  38. auto dotpr = [this](const OutputVertex& vtx) {
  39. switch (type) {
  40. case POS_X: return vtx.pos.x - vtx.pos.w;
  41. case NEG_X: return -vtx.pos.x - vtx.pos.w;
  42. case POS_Y: return vtx.pos.y - vtx.pos.w;
  43. case NEG_Y: return -vtx.pos.y - vtx.pos.w;
  44. // TODO: Verify z clipping
  45. case POS_Z: return vtx.pos.z - vtx.pos.w;
  46. default:
  47. case NEG_Z: return -vtx.pos.w;
  48. }
  49. };
  50. float24 dp = dotpr(v0);
  51. float24 dp_prev = dotpr(v1);
  52. float24 factor = dp_prev / (dp_prev - dp);
  53. return OutputVertex::Lerp(factor, v0, v1);
  54. }
  55. private:
  56. Type type;
  57. float24 pos;
  58. };
  59. static void InitScreenCoordinates(OutputVertex& vtx)
  60. {
  61. struct {
  62. float24 halfsize_x;
  63. float24 offset_x;
  64. float24 halfsize_y;
  65. float24 offset_y;
  66. float24 zscale;
  67. float24 offset_z;
  68. } viewport;
  69. viewport.halfsize_x = float24::FromRawFloat24(registers.viewport_size_x);
  70. viewport.halfsize_y = float24::FromRawFloat24(registers.viewport_size_y);
  71. viewport.offset_x = float24::FromFloat32(static_cast<float>(registers.viewport_corner.x));
  72. viewport.offset_y = float24::FromFloat32(static_cast<float>(registers.viewport_corner.y));
  73. viewport.zscale = float24::FromRawFloat24(registers.viewport_depth_range);
  74. viewport.offset_z = float24::FromRawFloat24(registers.viewport_depth_far_plane);
  75. float24 inv_w = float24::FromFloat32(1.f) / vtx.pos.w;
  76. vtx.color *= inv_w;
  77. vtx.tc0 *= inv_w;
  78. vtx.tc1 *= inv_w;
  79. vtx.tc2 *= inv_w;
  80. vtx.pos.w = inv_w;
  81. // TODO: Not sure why the viewport width needs to be divided by 2 but the viewport height does not
  82. vtx.screenpos[0] = (vtx.pos.x * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_x + viewport.offset_x;
  83. vtx.screenpos[1] = (vtx.pos.y * inv_w + float24::FromFloat32(1.0)) * viewport.halfsize_y + viewport.offset_y;
  84. vtx.screenpos[2] = viewport.offset_z - vtx.pos.z * inv_w * viewport.zscale;
  85. }
  86. void ProcessTriangle(OutputVertex &v0, OutputVertex &v1, OutputVertex &v2) {
  87. using boost::container::static_vector;
  88. // Clipping a planar n-gon against a plane will remove at least 1 vertex and introduces 2 at
  89. // the new edge (or less in degenerate cases). As such, we can say that each clipping plane
  90. // introduces at most 1 new vertex to the polygon. Since we start with a triangle and have a
  91. // fixed 6 clipping planes, the maximum number of vertices of the clipped polygon is 3 + 6 = 9.
  92. static const size_t MAX_VERTICES = 9;
  93. static_vector<OutputVertex, MAX_VERTICES> buffer_a = { v0, v1, v2 };
  94. static_vector<OutputVertex, MAX_VERTICES> buffer_b;
  95. auto* output_list = &buffer_a;
  96. auto* input_list = &buffer_b;
  97. // Simple implementation of the Sutherland-Hodgman clipping algorithm.
  98. // TODO: Make this less inefficient (currently lots of useless buffering overhead happens here)
  99. for (auto edge : { ClippingEdge(ClippingEdge::POS_X, float24::FromFloat32(+1.0)),
  100. ClippingEdge(ClippingEdge::NEG_X, float24::FromFloat32(-1.0)),
  101. ClippingEdge(ClippingEdge::POS_Y, float24::FromFloat32(+1.0)),
  102. ClippingEdge(ClippingEdge::NEG_Y, float24::FromFloat32(-1.0)),
  103. ClippingEdge(ClippingEdge::POS_Z, float24::FromFloat32(+1.0)),
  104. ClippingEdge(ClippingEdge::NEG_Z, float24::FromFloat32(-1.0)) }) {
  105. std::swap(input_list, output_list);
  106. output_list->clear();
  107. const OutputVertex* reference_vertex = &input_list->back();
  108. for (const auto& vertex : *input_list) {
  109. // NOTE: This algorithm changes vertex order in some cases!
  110. if (edge.IsInside(vertex)) {
  111. if (edge.IsOutSide(*reference_vertex)) {
  112. output_list->push_back(edge.GetIntersection(vertex, *reference_vertex));
  113. }
  114. output_list->push_back(vertex);
  115. } else if (edge.IsInside(*reference_vertex)) {
  116. output_list->push_back(edge.GetIntersection(vertex, *reference_vertex));
  117. }
  118. reference_vertex = &vertex;
  119. }
  120. // Need to have at least a full triangle to continue...
  121. if (output_list->size() < 3)
  122. return;
  123. }
  124. InitScreenCoordinates((*output_list)[0]);
  125. InitScreenCoordinates((*output_list)[1]);
  126. for (size_t i = 0; i < output_list->size() - 2; i ++) {
  127. OutputVertex& vtx0 = (*output_list)[0];
  128. OutputVertex& vtx1 = (*output_list)[i+1];
  129. OutputVertex& vtx2 = (*output_list)[i+2];
  130. InitScreenCoordinates(vtx2);
  131. LOG_TRACE(Render_Software,
  132. "Triangle %lu/%lu at position (%.3f, %.3f, %.3f, %.3f), "
  133. "(%.3f, %.3f, %.3f, %.3f), (%.3f, %.3f, %.3f, %.3f) and "
  134. "screen position (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f), (%.2f, %.2f, %.2f)",
  135. i, output_list->size(),
  136. vtx0.pos.x.ToFloat32(), vtx0.pos.y.ToFloat32(), vtx0.pos.z.ToFloat32(), vtx0.pos.w.ToFloat32(),
  137. vtx1.pos.x.ToFloat32(), vtx1.pos.y.ToFloat32(), vtx1.pos.z.ToFloat32(), vtx1.pos.w.ToFloat32(),
  138. vtx2.pos.x.ToFloat32(), vtx2.pos.y.ToFloat32(), vtx2.pos.z.ToFloat32(), vtx2.pos.w.ToFloat32(),
  139. vtx0.screenpos.x.ToFloat32(), vtx0.screenpos.y.ToFloat32(), vtx0.screenpos.z.ToFloat32(),
  140. vtx1.screenpos.x.ToFloat32(), vtx1.screenpos.y.ToFloat32(), vtx1.screenpos.z.ToFloat32(),
  141. vtx2.screenpos.x.ToFloat32(), vtx2.screenpos.y.ToFloat32(), vtx2.screenpos.z.ToFloat32());
  142. Rasterizer::ProcessTriangle(vtx0, vtx1, vtx2);
  143. }
  144. }
  145. } // namespace
  146. } // namespace