rendererAPI.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2019 to 2025 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #define DSR_INTERNAL_ACCESS
  24. #include "rendererAPI.h"
  25. #include "imageAPI.h"
  26. #include "drawAPI.h"
  27. #include "../implementation/render/renderCore.h"
  28. #include "../base/virtualStack.h"
  29. #include <limits>
  30. #define MUST_EXIST(OBJECT, METHOD) if (OBJECT.isNull()) { throwError("The " #OBJECT " handle was null in " #METHOD "\n"); }
  31. namespace dsr {
  32. static const int cellSize = 16;
  33. static bool counterClockwise(const ProjectedPoint& p, const ProjectedPoint& q, const ProjectedPoint& r) {
  34. return (q.flat.y - p.flat.y) * (r.flat.x - q.flat.x) - (q.flat.x - p.flat.x) * (r.flat.y - q.flat.y) < 0;
  35. }
  36. // outputHullCorners must be at least as big as inputHullCorners, so that it can hold the worst case output size.
  37. // Instead of not allowing less than three points, it copies the input as output when it happens to reduce pre-conditions.
  38. static void jarvisConvexHullAlgorithm(ProjectedPoint* outputHullCorners, int& outputCornerCount, const ProjectedPoint* inputHullCorners, int inputCornerCount) {
  39. if (inputCornerCount < 3) {
  40. outputCornerCount = inputCornerCount;
  41. for (int p = 0; p < inputCornerCount; p++) {
  42. outputHullCorners[p] = inputHullCorners[p];
  43. }
  44. } else {
  45. int l = 0;
  46. outputCornerCount = 0;
  47. for (int i = 1; i < inputCornerCount; i++) {
  48. if (inputHullCorners[i].flat.x < inputHullCorners[l].flat.x) {
  49. l = i;
  50. }
  51. }
  52. int p = l;
  53. do {
  54. if (outputCornerCount >= inputCornerCount) {
  55. // Prevent getting stuck in an infinite loop from overflow
  56. return;
  57. }
  58. outputHullCorners[outputCornerCount] = inputHullCorners[p]; outputCornerCount++;
  59. int q = (p + 1) % inputCornerCount;
  60. for (int i = 0; i < inputCornerCount; i++) {
  61. if (counterClockwise(inputHullCorners[p], inputHullCorners[i], inputHullCorners[q])) {
  62. q = i;
  63. }
  64. }
  65. p = q;
  66. } while (p != l);
  67. }
  68. }
  69. // Transform and project the corners of a hull, so that the output can be given to the convex hull algorithm and used for occluding
  70. // Returns true if occluder culling passed, which may skip occluders that could have been visible
  71. static bool projectHull(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
  72. for (int p = 0; p < cornerCount; p++) {
  73. FVector3D worldPoint = modelToWorldTransform.transformPoint(inputHullCorners[p]);
  74. FVector3D cameraPoint = camera.worldToCamera(worldPoint);
  75. FVector3D narrowPoint = cameraPoint * FVector3D(0.5f, 0.5f, 1.0f);
  76. for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
  77. FPlane3D plane = camera.cullFrustum.getPlane(s);
  78. if (!plane.inside(narrowPoint)) {
  79. return false;
  80. }
  81. }
  82. outputHullCorners[p] = camera.cameraToScreen(cameraPoint);
  83. }
  84. return true;
  85. }
  86. static IRect getPixelBoundFromProjection(const ProjectedPoint* convexHullCorners, int cornerCount) {
  87. IRect result = IRect(convexHullCorners[0].flat.x / constants::unitsPerPixel, convexHullCorners[0].flat.y / constants::unitsPerPixel, 1, 1);
  88. for (int p = 1; p < cornerCount; p++) {
  89. result = IRect::merge(result, IRect(convexHullCorners[p].flat.x / constants::unitsPerPixel, convexHullCorners[p].flat.y / constants::unitsPerPixel, 1, 1));
  90. }
  91. return result;
  92. }
  93. static bool pointInsideOfEdge(const LVector2D &edgeA, const LVector2D &edgeB, const LVector2D &point) {
  94. LVector2D edgeDirection = LVector2D(edgeB.y - edgeA.y, edgeA.x - edgeB.x);
  95. LVector2D relativePosition = point - edgeA;
  96. return (edgeDirection.x * relativePosition.x) + (edgeDirection.y * relativePosition.y) <= 0;
  97. }
  98. // Returns true iff the point is inside of the hull
  99. // convexHullCorners from 0 to cornerCount-1 must be sorted clockwise and may not include any concave corners
  100. static bool pointInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const LVector2D &point) {
  101. for (int c = 0; c < cornerCount; c++) {
  102. int nc = c + 1;
  103. if (nc == cornerCount) {
  104. nc = 0;
  105. }
  106. if (!pointInsideOfEdge(convexHullCorners[c].flat, convexHullCorners[nc].flat, point)) {
  107. // Outside of one edge, not inside
  108. return false;
  109. }
  110. }
  111. // Passed all edge tests
  112. return true;
  113. }
  114. // Returns true iff all corners of the rectangle are inside of the hull
  115. static bool rectangleInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect &rectangle) {
  116. return pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.top()))
  117. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.top()))
  118. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.bottom()))
  119. && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.bottom()));
  120. }
  121. struct DebugLine {
  122. int64_t x1, y1, x2, y2;
  123. ColorRgbaI32 color;
  124. DebugLine(int64_t x1, int64_t y1, int64_t x2, int64_t y2, const ColorRgbaI32& color)
  125. : x1(x1), y1(y1), x2(x2), y2(y2), color(color) {}
  126. };
  127. // Context for multi-threaded rendering of triangles in a command queue
  128. struct RendererImpl {
  129. bool receiving = false; // Preventing version dependency by only allowing calls in the expected order
  130. ImageRgbaU8 colorBuffer; // The color image being rendered to
  131. ImageF32 depthBuffer; // Linear depth for isometric cameras, 1 / depth for perspective cameras
  132. ImageF32 depthGrid; // An occlusion grid of cellSize² cells representing the longest linear depth where something might be visible
  133. CommandQueue commandQueue; // Triangles to be drawn
  134. List<DebugLine> debugLines; // Additional lines to be drawn as an overlay for debugging occlusion
  135. int width = 0, height = 0, gridWidth = 0, gridHeight = 0;
  136. bool occluded = false;
  137. RendererImpl() {}
  138. void beginFrame(ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
  139. if (this->receiving) {
  140. throwError("Called renderer_begin on the same renderer twice without ending the previous batch!\n");
  141. }
  142. this->receiving = true;
  143. this->colorBuffer = colorBuffer;
  144. this->depthBuffer = depthBuffer;
  145. if (image_exists(this->colorBuffer)) {
  146. this->width = image_getWidth(this->colorBuffer);
  147. this->height = image_getHeight(this->colorBuffer);
  148. } else if (image_exists(this->depthBuffer)) {
  149. this->width = image_getWidth(this->depthBuffer);
  150. this->height = image_getHeight(this->depthBuffer);
  151. }
  152. this->gridWidth = (this->width + (cellSize - 1)) / cellSize;
  153. this->gridHeight = (this->height + (cellSize - 1)) / cellSize;
  154. this->occluded = false;
  155. }
  156. IRect getOuterCellBound(const IRect &pixelBound) const {
  157. int minCellX = pixelBound.left() / cellSize;
  158. int maxCellX = pixelBound.right() / cellSize + 1;
  159. int minCellY = pixelBound.top() / cellSize;
  160. int maxCellY = pixelBound.bottom() / cellSize + 1;
  161. if (minCellX < 0) { minCellX = 0; }
  162. if (minCellY < 0) { minCellY = 0; }
  163. if (maxCellX > this->gridWidth) { maxCellX = this->gridWidth; }
  164. if (maxCellY > this->gridHeight) { maxCellY = this->gridHeight; }
  165. return IRect(minCellX, minCellY, maxCellX - minCellX, maxCellY - minCellY);
  166. }
  167. // Called before occluding so that the grid is initialized once when used and skipped when not used
  168. void prepareForOcclusion() {
  169. if (!this->occluded) {
  170. // Allocate the grid if a sufficiently large one does not already exist
  171. if (!(image_exists(this->depthGrid) && image_getWidth(this->depthGrid) >= gridWidth && image_getHeight(this->depthGrid) >= gridHeight)) {
  172. this->depthGrid = image_create_F32(gridWidth, gridHeight);
  173. }
  174. // Use inifnite depth in camera space
  175. image_fill(this->depthGrid, std::numeric_limits<float>::infinity());
  176. }
  177. this->occluded = true;
  178. }
  179. // If any occluder has been used during this pass, all triangles in the buffer will be filtered based using depthGrid
  180. void completeOcclusion() {
  181. if (this->occluded) {
  182. for (int t = this->commandQueue.buffer.length() - 1; t >= 0; t--) {
  183. bool anyVisible = false;
  184. ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
  185. IRect outerBound = getOuterCellBound(triangle.wholeBound);
  186. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  187. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  188. // TODO: Optimize access using SafePointer iteration
  189. float backgroundDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  190. float triangleDepth = triangle.position[0].cs.z;
  191. replaceWithSmaller(triangleDepth, triangle.position[1].cs.z);
  192. replaceWithSmaller(triangleDepth, triangle.position[2].cs.z);
  193. if (triangleDepth < backgroundDepth + 0.001) {
  194. anyVisible = true;
  195. }
  196. }
  197. }
  198. if (!anyVisible) {
  199. // TODO: Make triangle swapping work so that the list can be sorted
  200. this->commandQueue.buffer[t].occluded = true;
  201. }
  202. }
  203. }
  204. }
  205. void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect& pixelBound) {
  206. // Loop over the outer bound
  207. if (pixelBound.width() > cellSize && pixelBound.height() > cellSize) {
  208. float distance = 0.0f;
  209. for (int c = 0; c < cornerCount; c++) {
  210. replaceWithLarger(distance, convexHullCorners[c].cs.z);
  211. }
  212. // Loop over all cells within the bound
  213. IRect outerBound = getOuterCellBound(pixelBound);
  214. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  215. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  216. IRect pixelRegion = IRect(cellX * cellSize, cellY * cellSize, cellSize, cellSize);
  217. IRect subPixelRegion = pixelRegion * constants::unitsPerPixel;
  218. if (rectangleInsideOfHull(convexHullCorners, cornerCount, subPixelRegion)) {
  219. float oldDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  220. if (distance < oldDepth) {
  221. image_writePixel(this->depthGrid, cellX, cellY, distance);
  222. }
  223. }
  224. }
  225. }
  226. }
  227. }
  228. void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount) {
  229. occludeFromSortedHull(convexHullCorners, cornerCount, getPixelBoundFromProjection(convexHullCorners, cornerCount));
  230. }
  231. void occludeFromExistingTriangles() {
  232. if (!this->receiving) {
  233. throwError("Cannot call renderer_occludeFromExistingTriangles without first calling renderer_begin!\n");
  234. }
  235. prepareForOcclusion();
  236. // Generate a depth grid to remove many small triangles behind larger triangles
  237. // This will leave triangles along seams but at least begin to remove the worst unwanted drawing
  238. for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
  239. // Get the current triangle from the queue
  240. Filter filter = this->commandQueue.buffer[t].filter;
  241. if (filter == Filter::Solid) {
  242. ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
  243. occludeFromSortedHull(triangle.position, 3, triangle.wholeBound);
  244. }
  245. }
  246. }
  247. #define GENERATE_BOX_CORNERS(TARGET, MIN, MAX) \
  248. TARGET[0] = FVector3D(MIN.x, MIN.y, MIN.z); \
  249. TARGET[1] = FVector3D(MIN.x, MIN.y, MAX.z); \
  250. TARGET[2] = FVector3D(MIN.x, MAX.y, MIN.z); \
  251. TARGET[3] = FVector3D(MIN.x, MAX.y, MAX.z); \
  252. TARGET[4] = FVector3D(MAX.x, MIN.y, MIN.z); \
  253. TARGET[5] = FVector3D(MAX.x, MIN.y, MAX.z); \
  254. TARGET[6] = FVector3D(MAX.x, MAX.y, MIN.z); \
  255. TARGET[7] = FVector3D(MAX.x, MAX.y, MAX.z);
  256. // Fills the occlusion grid using the box, so that things behind it can skip rendering
  257. void occludeFromBox(const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
  258. if (!this->receiving) {
  259. throwError("Cannot call renderer_occludeFromBox without first calling renderer_begin!\n");
  260. }
  261. prepareForOcclusion();
  262. static const int pointCount = 8;
  263. FVector3D localPoints[pointCount];
  264. ProjectedPoint projections[pointCount];
  265. ProjectedPoint edgeCorners[pointCount];
  266. GENERATE_BOX_CORNERS(localPoints, minimum, maximum)
  267. if (projectHull(projections, localPoints, 8, modelToWorldTransform, camera)) {
  268. // Get a 2D convex hull from the projected corners
  269. int edgeCornerCount = 0;
  270. jarvisConvexHullAlgorithm(edgeCorners, edgeCornerCount, projections, 8);
  271. occludeFromSortedHull(edgeCorners, edgeCornerCount);
  272. // Allow saving the 2D silhouette for debugging
  273. if (debugSilhouette) {
  274. for (int p = 0; p < edgeCornerCount; p++) {
  275. int q = (p + 1) % edgeCornerCount;
  276. if (projections[p].cs.z > camera.nearClip) {
  277. this->debugLines.pushConstruct(
  278. edgeCorners[p].flat.x / constants::unitsPerPixel, edgeCorners[p].flat.y / constants::unitsPerPixel,
  279. edgeCorners[q].flat.x / constants::unitsPerPixel, edgeCorners[q].flat.y / constants::unitsPerPixel,
  280. ColorRgbaI32(0, 255, 255, 255)
  281. );
  282. }
  283. }
  284. }
  285. }
  286. }
  287. // Occlusion test for whole model bounds.
  288. // Returns false if the convex hull of the corners has a chance to be seen from the camera.
  289. bool isHullOccluded(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) const {
  290. VirtualStackAllocation<FVector3D> cameraPoints(cornerCount);
  291. for (int p = 0; p < cornerCount; p++) {
  292. cameraPoints[p] = camera.worldToCamera(modelToWorldTransform.transformPoint(inputHullCorners[p]));
  293. outputHullCorners[p] = camera.cameraToScreen(cameraPoints[p]);
  294. }
  295. // Culling test to see if all points are outside of the same plane of the view frustum.
  296. for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
  297. bool allOutside = true; // True until prooven false.
  298. FPlane3D plane = camera.cullFrustum.getPlane(s);
  299. for (int p = 0; p < cornerCount; p++) {
  300. if (plane.inside(cameraPoints[p])) {
  301. // One point was inside of this plane, so it can not guarantee that all interpolated points between the corners are outside.
  302. allOutside = false;
  303. break;
  304. }
  305. }
  306. // If all points are outside of the same plane in the view frustum...
  307. if (allOutside) {
  308. // ...then we know that all interpolated points in between are also outside of this plane.
  309. return true; // Occluded due to failing culling test.
  310. }
  311. }
  312. IRect pixelBound = getPixelBoundFromProjection(outputHullCorners, cornerCount);
  313. float closestDistance = std::numeric_limits<float>::infinity();
  314. for (int c = 0; c < cornerCount; c++) {
  315. replaceWithSmaller(closestDistance, outputHullCorners[c].cs.z);
  316. }
  317. // Loop over all cells within the bound
  318. IRect outerBound = getOuterCellBound(pixelBound);
  319. for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
  320. for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
  321. if (closestDistance < image_readPixel_clamp(this->depthGrid, cellX, cellY)) {
  322. return false; // Visible because one cell had a more distant maximum depth.
  323. }
  324. }
  325. }
  326. return true; // Occluded, because none of the cells had a more distant depth.
  327. }
  328. // Checks if the box from minimum to maximum in object space is fully occluded when seen by the camera
  329. // Must be the same camera as when occluders filled the grid with occlusion depth
  330. bool isBoxOccluded(const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) const {
  331. if (!this->receiving) {
  332. throwError("Cannot call renderer_isBoxVisible without first calling renderer_begin and giving occluder shapes to the pass!\n");
  333. }
  334. FVector3D corners[8];
  335. GENERATE_BOX_CORNERS(corners, minimum, maximum)
  336. ProjectedPoint projections[8];
  337. return isHullOccluded(projections, corners, 8, modelToWorldTransform, camera);
  338. }
  339. void endFrame(bool debugWireframe) {
  340. if (!this->receiving) {
  341. throwError("Called renderer_end without renderer_begin!\n");
  342. }
  343. this->receiving = false;
  344. // Mark occluded triangles to prevent them from being rendered
  345. completeOcclusion();
  346. this->commandQueue.execute(IRect::FromSize(this->width, this->height));
  347. if (image_exists(this->colorBuffer)) {
  348. // Debug drawn triangles
  349. if (debugWireframe) {
  350. /*if (image_exists(this->depthGrid)) {
  351. for (int cellY = 0; cellY < this->gridHeight; cellY++) {
  352. for (int cellX = 0; cellX < this->gridWidth; cellX++) {
  353. float depth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
  354. if (depth < std::numeric_limits<float>::infinity()) {
  355. int intensity = depth;
  356. draw_rectangle(this->colorBuffer, IRect(cellX * cellSize + 4, cellY * cellSize + 4, cellSize - 8, cellSize - 8), ColorRgbaI32(intensity, intensity, 0, 255));
  357. }
  358. }
  359. }
  360. }*/
  361. for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
  362. if (!this->commandQueue.buffer[t].occluded) {
  363. ITriangle2D *triangle = &(this->commandQueue.buffer[t].triangle);
  364. draw_line(this->colorBuffer,
  365. triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
  366. triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
  367. ColorRgbaI32(255, 255, 255, 255)
  368. );
  369. draw_line(this->colorBuffer,
  370. triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
  371. triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
  372. ColorRgbaI32(255, 255, 255, 255)
  373. );
  374. draw_line(this->colorBuffer,
  375. triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
  376. triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
  377. ColorRgbaI32(255, 255, 255, 255)
  378. );
  379. }
  380. }
  381. }
  382. // Debug anything else added to debugLines
  383. for (int l = 0; l < this->debugLines.length(); l++) {
  384. draw_line(this->colorBuffer, this->debugLines[l].x1, this->debugLines[l].y1, this->debugLines[l].x2, this->debugLines[l].y2, this->debugLines[l].color);
  385. }
  386. this->debugLines.clear();
  387. }
  388. this->commandQueue.clear();
  389. }
  390. void occludeFromTopRows(const Camera &camera) {
  391. // Make sure that the depth grid exists with the correct dimensions.
  392. this->prepareForOcclusion();
  393. if (!this->receiving) {
  394. throwError("Cannot call renderer_occludeFromTopRows without first calling renderer_begin!\n");
  395. }
  396. if (!image_exists(this->depthBuffer)) {
  397. throwError("Cannot call renderer_occludeFromTopRows without having given a depth buffer in renderer_begin!\n");
  398. }
  399. SafePointer<float> depthRow = image_getSafePointer(this->depthBuffer);
  400. int depthStride = image_getStride(this->depthBuffer);
  401. SafePointer<float> gridRow = image_getSafePointer(this->depthGrid);
  402. int gridStride = image_getStride(this->depthGrid);
  403. if (camera.perspective) {
  404. // Perspective case using 1/depth for the depth buffer.
  405. for (int y = 0; y < this->height; y += cellSize) {
  406. SafePointer<float> gridPixel = gridRow;
  407. SafePointer<float> depthPixel = depthRow;
  408. int x = 0;
  409. int right = cellSize - 1;
  410. float maxInvDistance;
  411. // Scan bottom row of whole cell width
  412. for (int gridX = 0; gridX < this->gridWidth; gridX++) {
  413. maxInvDistance = std::numeric_limits<float>::infinity();
  414. if (right >= this->width) { right = this->width; }
  415. while (x < right) {
  416. float newInvDistance = *depthPixel;
  417. if (newInvDistance < maxInvDistance) { maxInvDistance = newInvDistance; }
  418. depthPixel += 1;
  419. x += 1;
  420. }
  421. float maxDistance = 1.0f / maxInvDistance;
  422. float oldDistance = *gridPixel;
  423. if (maxDistance < oldDistance) {
  424. *gridPixel = maxDistance;
  425. }
  426. gridPixel += 1;
  427. right += cellSize;
  428. }
  429. // Go to the next grid row
  430. depthRow.increaseBytes(depthStride * cellSize);
  431. gridRow.increaseBytes(gridStride);
  432. }
  433. } else {
  434. // Orthogonal case where linear depth is used for both grid and depth buffer.
  435. // TODO: Create test cases for many ways to use occlusion, even these strange cases like isometric occlusion where plain culling does not leave many occluded models.
  436. for (int y = 0; y < this->height; y += cellSize) {
  437. SafePointer<float> gridPixel = gridRow;
  438. SafePointer<float> depthPixel = depthRow;
  439. int x = 0;
  440. int right = cellSize - 1;
  441. float maxDistance;
  442. // Scan bottom row of whole cell width
  443. for (int gridX = 0; gridX < this->gridWidth; gridX++) {
  444. maxDistance = 0.0f;
  445. if (right >= this->width) { right = this->width; }
  446. while (x < right) {
  447. float newDistance = *depthPixel;
  448. if (newDistance > maxDistance) { maxDistance = newDistance; }
  449. depthPixel += 1;
  450. x += 1;
  451. }
  452. float oldDistance = *gridPixel;
  453. if (maxDistance < oldDistance) {
  454. *gridPixel = maxDistance;
  455. }
  456. gridPixel += 1;
  457. right += cellSize;
  458. }
  459. // Go to the next grid row
  460. depthRow.increaseBytes(depthStride * cellSize);
  461. gridRow.increaseBytes(gridStride);
  462. }
  463. }
  464. }
  465. };
  466. ImageRgbaU8 renderer_getColorBuffer(const Renderer& renderer) {
  467. MUST_EXIST(renderer, renderer_getColorBuffer);
  468. return renderer->receiving ? renderer->colorBuffer : ImageRgbaU8();
  469. }
  470. ImageF32 renderer_getDepthBuffer(const Renderer& renderer) {
  471. MUST_EXIST(renderer, renderer_getDepthBuffer);
  472. return renderer->receiving ? renderer->depthBuffer : ImageF32();
  473. }
  474. Renderer renderer_create() {
  475. return handle_create<RendererImpl>().setName("Renderer");
  476. }
  477. bool renderer_exists(const Renderer& renderer) {
  478. return renderer.isNotNull();
  479. }
  480. void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
  481. MUST_EXIST(renderer, renderer_begin);
  482. renderer->beginFrame(colorBuffer, depthBuffer);
  483. }
  484. void renderer_giveTask_triangle(Renderer& renderer,
  485. const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
  486. const FVector4D &colorA, const FVector4D &colorB, const FVector4D &colorC,
  487. const FVector4D &texCoordA, const FVector4D &texCoordB, const FVector4D &texCoordC,
  488. const TextureRgbaU8& diffuseMap, const TextureRgbaU8& lightMap,
  489. Filter filter, const Camera &camera) {
  490. #ifndef NDEBUG
  491. MUST_EXIST(renderer, renderer_addTriangle);
  492. #endif
  493. renderTriangleFromData(
  494. &(renderer->commandQueue), renderer->colorBuffer, renderer->depthBuffer, camera,
  495. posA, posB, posC,
  496. filter, diffuseMap, lightMap,
  497. TriangleTexCoords(texCoordA, texCoordB, texCoordC),
  498. TriangleColors(colorA, colorB, colorC)
  499. );
  500. }
  501. void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
  502. #ifndef NDEBUG
  503. MUST_EXIST(renderer, renderer_occludeFromBox);
  504. #endif
  505. renderer->occludeFromBox(minimum, maximum, modelToWorldTransform, camera, debugSilhouette);
  506. }
  507. void renderer_occludeFromExistingTriangles(Renderer& renderer) {
  508. MUST_EXIST(renderer, renderer_optimize);
  509. renderer->occludeFromExistingTriangles();
  510. }
  511. void renderer_occludeFromTopRows(Renderer& renderer, const Camera &camera) {
  512. MUST_EXIST(renderer, renderer_occludeFromTopRows);
  513. renderer->occludeFromTopRows(camera);
  514. }
  515. bool renderer_isBoxVisible(const Renderer& renderer, const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) {
  516. #ifndef NDEBUG
  517. MUST_EXIST(renderer, renderer_isBoxVisible);
  518. #endif
  519. return !(renderer->isBoxOccluded(minimum, maximum, modelToWorldTransform, camera));
  520. }
  521. void renderer_end(Renderer& renderer, bool debugWireframe) {
  522. MUST_EXIST(renderer, renderer_end);
  523. renderer->endFrame(debugWireframe);
  524. }
  525. bool renderer_takesTriangles(const Renderer& renderer) {
  526. #ifndef NDEBUG
  527. MUST_EXIST(renderer, renderer_isReceivingTriangles);
  528. #endif
  529. return renderer->receiving;
  530. }
  531. bool renderer_hasOccluders(const Renderer& renderer) {
  532. #ifndef NDEBUG
  533. MUST_EXIST(renderer, renderer_hasOccluders);
  534. #endif
  535. return renderer->occluded;
  536. }
  537. }