Browse Source

Moved outrendering logic from modelAPI.h and replaced raw pointers with reference counting.

David Piuva 7 months ago
parent
commit
e69f53aaaf

+ 62 - 521
Source/DFPSR/api/modelAPI.cpp

@@ -26,6 +26,7 @@
 #include "modelAPI.h"
 #include "modelAPI.h"
 #include "imageAPI.h"
 #include "imageAPI.h"
 #include "drawAPI.h"
 #include "drawAPI.h"
+// TODO: Inline as much as possible from Model.h to modelAPI.cpp, to reduce call depth and make it easy to copy and modify the model implementation.
 #include "../implementation/render/model/Model.h"
 #include "../implementation/render/model/Model.h"
 #include "../base/virtualStack.h"
 #include "../base/virtualStack.h"
 #include <limits>
 #include <limits>
@@ -195,12 +196,12 @@ void model_setLightMapByName(Model& model, int partIndex, ResourcePool &pool, co
 // Single-threaded rendering for the simple cases where you just want it to work
 // Single-threaded rendering for the simple cases where you just want it to work
 void model_render(const Model& model, const Transform3D &modelToWorldTransform, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer, const Camera &camera) {
 void model_render(const Model& model, const Transform3D &modelToWorldTransform, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer, const Camera &camera) {
 	if (model.isNotNull()) {
 	if (model.isNotNull()) {
-		model->render((CommandQueue*)nullptr, image_exists(colorBuffer) ? &colorBuffer : nullptr, image_exists(depthBuffer) ? &depthBuffer : nullptr, modelToWorldTransform, camera);
+		model->render((CommandQueue*)nullptr, colorBuffer, depthBuffer, modelToWorldTransform, camera);
 	}
 	}
 }
 }
 void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera) {
 void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera) {
 	if (model.isNotNull()) {
 	if (model.isNotNull()) {
-		model->renderDepth(image_exists(depthBuffer) ? &depthBuffer : nullptr, modelToWorldTransform, camera);
+		model->renderDepth(depthBuffer, modelToWorldTransform, camera);
 	}
 	}
 }
 }
 
 
@@ -210,533 +211,73 @@ void model_getBoundingBox(const Model& model, FVector3D& minimum, FVector3D& max
 	maximum = model->maxBound;
 	maximum = model->maxBound;
 }
 }
 
 
-static const int cellSize = 16;
-
-struct DebugLine {
-	int64_t x1, y1, x2, y2;
-	ColorRgbaI32 color;
-	DebugLine(int64_t x1, int64_t y1, int64_t x2, int64_t y2, const ColorRgbaI32& color)
-	: x1(x1), y1(y1), x2(x2), y2(y2), color(color) {}
-};
-
-// Context for multi-threaded rendering of triangles in a command queue
-struct RendererImpl {
-	bool receiving = false; // Preventing version dependency by only allowing calls in the expected order
-	ImageRgbaU8 colorBuffer; // The color image being rendered to
-	ImageF32 depthBuffer; // Linear depth for isometric cameras, 1 / depth for perspective cameras
-	ImageF32 depthGrid; // An occlusion grid of cellSize² cells representing the longest linear depth where something might be visible
-	CommandQueue commandQueue; // Triangles to be drawn
-	List<DebugLine> debugLines; // Additional lines to be drawn as an overlay for debugging occlusion
-	int width = 0, height = 0, gridWidth = 0, gridHeight = 0;
-	bool occluded = false;
-	RendererImpl() {}
-	void beginFrame(ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
-		if (this->receiving) {
-			throwError("Called renderer_begin on the same renderer twice without ending the previous batch!\n");
-		}
-		this->receiving = true;
-		this->colorBuffer = colorBuffer;
-		this->depthBuffer = depthBuffer;
-		if (image_exists(this->colorBuffer)) {
-			this->width = image_getWidth(this->colorBuffer);
-			this->height = image_getHeight(this->colorBuffer);
-		} else if (image_exists(this->depthBuffer)) {
-			this->width = image_getWidth(this->depthBuffer);
-			this->height = image_getHeight(this->depthBuffer);
-		}
-		this->gridWidth = (this->width + (cellSize - 1)) / cellSize;
-		this->gridHeight = (this->height + (cellSize - 1)) / cellSize;
-		this->occluded = false;
-	}
-	bool pointInsideOfEdge(const LVector2D &edgeA, const LVector2D &edgeB, const LVector2D &point) {
-		LVector2D edgeDirection = LVector2D(edgeB.y - edgeA.y, edgeA.x - edgeB.x);
-		LVector2D relativePosition = point - edgeA;
-		return (edgeDirection.x * relativePosition.x) + (edgeDirection.y * relativePosition.y) <= 0;
-	}
-	// Returns true iff the point is inside of the hull
-	// convexHullCorners from 0 to cornerCount-1 must be sorted clockwise and may not include any concave corners
-	bool pointInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const LVector2D &point) {
-		for (int c = 0; c < cornerCount; c++) {
-			int nc = c + 1;
-			if (nc == cornerCount) {
-				nc = 0;
-			}
-			if (!pointInsideOfEdge(convexHullCorners[c].flat, convexHullCorners[nc].flat, point)) {
-				// Outside of one edge, not inside
-				return false;
-			}
-		}
-		// Passed all edge tests
-		return true;
-	}
-	// Returns true iff all corners of the rectangle are inside of the hull
-	bool rectangleInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect &rectangle) {
-		return pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.top()))
-		    && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.top()))
-		    && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.bottom()))
-		    && pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.bottom()));
-	}
-	IRect getOuterCellBound(const IRect &pixelBound) {
-		int minCellX = pixelBound.left() / cellSize;
-		int maxCellX = pixelBound.right() / cellSize + 1;
-		int minCellY = pixelBound.top() / cellSize;
-		int maxCellY = pixelBound.bottom() / cellSize + 1;
-		if (minCellX < 0) { minCellX = 0; }
-		if (minCellY < 0) { minCellY = 0; }
-		if (maxCellX > this->gridWidth) { maxCellX = this->gridWidth; }
-		if (maxCellY > this->gridHeight) { maxCellY = this->gridHeight; }
-		return IRect(minCellX, minCellY, maxCellX - minCellX, maxCellY - minCellY);
-	}
-	// Called before occluding so that the grid is initialized once when used and skipped when not used
-	void prepareForOcclusion() {
-		if (!this->occluded) {
-			// Allocate the grid if a sufficiently large one does not already exist
-			if (!(image_exists(this->depthGrid) && image_getWidth(this->depthGrid) >= gridWidth && image_getHeight(this->depthGrid) >= gridHeight)) {
-				this->depthGrid = image_create_F32(gridWidth, gridHeight);
-			}
-			// Use inifnite depth in camera space
-			image_fill(this->depthGrid, std::numeric_limits<float>::infinity());
-		}
-		this->occluded = true;
-	}
-	// If any occluder has been used during this pass, all triangles in the buffer will be filtered based using depthGrid
-	void completeOcclusion() {
-		if (this->occluded) {
-			for (int t = this->commandQueue.buffer.length() - 1; t >= 0; t--) {
-				bool anyVisible = false;
-				ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
-				IRect outerBound = getOuterCellBound(triangle.wholeBound);
-				for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
-					for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
-						// TODO: Optimize access using SafePointer iteration
-						float backgroundDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
-						float triangleDepth = triangle.position[0].cs.z;
-						replaceWithSmaller(triangleDepth, triangle.position[1].cs.z);
-						replaceWithSmaller(triangleDepth, triangle.position[2].cs.z);
-						if (triangleDepth < backgroundDepth + 0.001) {
-							anyVisible = true;
-						}
-					}
-				}
-				if (!anyVisible) {
-					// TODO: Make triangle swapping work so that the list can be sorted
-					this->commandQueue.buffer[t].occluded = true;
-				}
-			}
-		}
-	}
-	void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect& pixelBound) {
-		// Loop over the outer bound
-		if (pixelBound.width() > cellSize && pixelBound.height() > cellSize) {
-			float distance = 0.0f;
-			for (int c = 0; c < cornerCount; c++) {
-				replaceWithLarger(distance, convexHullCorners[c].cs.z);
-			}
-			// Loop over all cells within the bound
-			IRect outerBound = getOuterCellBound(pixelBound);
-			for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
-				for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
-					IRect pixelRegion = IRect(cellX * cellSize, cellY * cellSize, cellSize, cellSize);
-					IRect subPixelRegion = pixelRegion * constants::unitsPerPixel;
-					if (rectangleInsideOfHull(convexHullCorners, cornerCount, subPixelRegion)) {
-						float oldDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
-						if (distance < oldDepth) {
-							image_writePixel(this->depthGrid, cellX, cellY, distance);
-						}
-					}
-				}
-			}
-		}
-	}
-	IRect getPixelBoundFromProjection(const ProjectedPoint* convexHullCorners, int cornerCount) {
-		IRect result = IRect(convexHullCorners[0].flat.x / constants::unitsPerPixel, convexHullCorners[0].flat.y / constants::unitsPerPixel, 1, 1);
-		for (int p = 1; p < cornerCount; p++) {
-			result = IRect::merge(result, IRect(convexHullCorners[p].flat.x / constants::unitsPerPixel, convexHullCorners[p].flat.y / constants::unitsPerPixel, 1, 1));
-		}
-		return result;
-	}
-	void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount) {
-		occludeFromSortedHull(convexHullCorners, cornerCount, getPixelBoundFromProjection(convexHullCorners, cornerCount));
-	}
-	void occludeFromExistingTriangles() {
-		if (!this->receiving) {
-			throwError("Cannot call renderer_occludeFromExistingTriangles without first calling renderer_begin!\n");
-		}
-		prepareForOcclusion();
-		// Generate a depth grid to remove many small triangles behind larger triangles
-		//   This will leave triangles along seams but at least begin to remove the worst unwanted drawing
-		for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
-			// Get the current triangle from the queue
-			Filter filter = this->commandQueue.buffer[t].filter;
-			if (filter == Filter::Solid) {
-				ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
-				occludeFromSortedHull(triangle.position, 3, triangle.wholeBound);
-			}
-		}
-	}
-	bool counterClockwise(const ProjectedPoint& p, const ProjectedPoint& q, const ProjectedPoint& r) {
-		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;
-	}
-	// outputHullCorners must be at least as big as inputHullCorners, so that it can hold the worst case output size.
-	// Instead of not allowing less than three points, it copies the input as output when it happens to reduce pre-conditions.
-	void jarvisConvexHullAlgorithm(ProjectedPoint* outputHullCorners, int& outputCornerCount, const ProjectedPoint* inputHullCorners, int inputCornerCount) {
-		if (inputCornerCount < 3) {
-			outputCornerCount = inputCornerCount;
-			for (int p = 0; p < inputCornerCount; p++) {
-				outputHullCorners[p] = inputHullCorners[p];
-			}
-		} else {
-			int l = 0;
-			outputCornerCount = 0;
-			for (int i = 1; i < inputCornerCount; i++) {
-				if (inputHullCorners[i].flat.x < inputHullCorners[l].flat.x) {
-					l = i;
-				}
-			}
-			int p = l;
-			do {
-				if (outputCornerCount >= inputCornerCount) {
-					// Prevent getting stuck in an infinite loop from overflow
-					return;
-				}
-				outputHullCorners[outputCornerCount] = inputHullCorners[p]; outputCornerCount++;
-				int q = (p + 1) % inputCornerCount;
-				for (int i = 0; i < inputCornerCount; i++) {
-					if (counterClockwise(inputHullCorners[p], inputHullCorners[i], inputHullCorners[q])) {
-						q = i;
-					}
-				}
-				p = q;
-			} while (p != l);
-		}
-	}
-	// Transform and project the corners of a hull, so that the output can be given to the convex hull algorithm and used for occluding
-	// Returns true if occluder culling passed, which may skip occluders that could have been visible
-	bool projectHull(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
-		for (int p = 0; p < cornerCount; p++) {
-			FVector3D worldPoint = modelToWorldTransform.transformPoint(inputHullCorners[p]);
-			FVector3D cameraPoint = camera.worldToCamera(worldPoint);
-			FVector3D narrowPoint = cameraPoint * FVector3D(0.5f, 0.5f, 1.0f);
-			for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
-				FPlane3D plane = camera.cullFrustum.getPlane(s);
-				if (!plane.inside(narrowPoint)) {
-					return false;
-				}
-			}
-			outputHullCorners[p] = camera.cameraToScreen(cameraPoint);
-		}
-		return true;
-	}
-	#define GENERATE_BOX_CORNERS(TARGET, MIN, MAX) \
-		TARGET[0] = FVector3D(MIN.x, MIN.y, MIN.z); \
-		TARGET[1] = FVector3D(MIN.x, MIN.y, MAX.z); \
-		TARGET[2] = FVector3D(MIN.x, MAX.y, MIN.z); \
-		TARGET[3] = FVector3D(MIN.x, MAX.y, MAX.z); \
-		TARGET[4] = FVector3D(MAX.x, MIN.y, MIN.z); \
-		TARGET[5] = FVector3D(MAX.x, MIN.y, MAX.z); \
-		TARGET[6] = FVector3D(MAX.x, MAX.y, MIN.z); \
-		TARGET[7] = FVector3D(MAX.x, MAX.y, MAX.z);
-	// Fills the occlusion grid using the box, so that things behind it can skip rendering
-	void occludeFromBox(const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
-		if (!this->receiving) {
-			throwError("Cannot call renderer_occludeFromBox without first calling renderer_begin!\n");
-		}
-		prepareForOcclusion();
-		static const int pointCount = 8;
-		FVector3D localPoints[pointCount];
-		ProjectedPoint projections[pointCount];
-		ProjectedPoint edgeCorners[pointCount];
-		GENERATE_BOX_CORNERS(localPoints, minimum, maximum)
-		if (projectHull(projections, localPoints, 8, modelToWorldTransform, camera)) {
-			// Get a 2D convex hull from the projected corners
-			int edgeCornerCount = 0;
-			jarvisConvexHullAlgorithm(edgeCorners, edgeCornerCount, projections, 8);
-			occludeFromSortedHull(edgeCorners, edgeCornerCount);
-			// Allow saving the 2D silhouette for debugging
-			if (debugSilhouette) {
-				for (int p = 0; p < edgeCornerCount; p++) {
-					int q = (p + 1) % edgeCornerCount;
-					if (projections[p].cs.z > camera.nearClip) {
-						this->debugLines.pushConstruct(
-						  edgeCorners[p].flat.x / constants::unitsPerPixel, edgeCorners[p].flat.y / constants::unitsPerPixel,
-						  edgeCorners[q].flat.x / constants::unitsPerPixel, edgeCorners[q].flat.y / constants::unitsPerPixel,
-						  ColorRgbaI32(0, 255, 255, 255)
-						);
-					}
-				}
-			}
-		}
-	}
-	// Occlusion test for whole model bounds.
-	// Returns false if the convex hull of the corners has a chance to be seen from the camera.
-	bool isHullOccluded(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
-		VirtualStackAllocation<FVector3D> cameraPoints(cornerCount);
-		for (int p = 0; p < cornerCount; p++) {
-			cameraPoints[p] = camera.worldToCamera(modelToWorldTransform.transformPoint(inputHullCorners[p]));
-			outputHullCorners[p] = camera.cameraToScreen(cameraPoints[p]);
-		}
-		// Culling test to see if all points are outside of the same plane of the view frustum.
-		for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
-			bool allOutside = true; // True until prooven false.
-			FPlane3D plane = camera.cullFrustum.getPlane(s);
-			for (int p = 0; p < cornerCount; p++) {
-				if (plane.inside(cameraPoints[p])) {
-					// One point was inside of this plane, so it can not guarantee that all interpolated points between the corners are outside.
-					allOutside = false;
-					break;
-				}
-			}
-			// If all points are outside of the same plane in the view frustum...
-			if (allOutside) {
-				// ...then we know that all interpolated points in between are also outside of this plane.
-				return true; // Occluded due to failing culling test.
-			}
-		}
-		IRect pixelBound = getPixelBoundFromProjection(outputHullCorners, cornerCount);
-		float closestDistance = std::numeric_limits<float>::infinity();
-		for (int c = 0; c < cornerCount; c++) {
-			replaceWithSmaller(closestDistance, outputHullCorners[c].cs.z);
-		}
-		// Loop over all cells within the bound
-		IRect outerBound = getOuterCellBound(pixelBound);
-		for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
-			for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
-				if (closestDistance < image_readPixel_clamp(this->depthGrid, cellX, cellY)) {
-					return false; // Visible because one cell had a more distant maximum depth.
-				}
-			}
-		}
-		return true; // Occluded, because none of the cells had a more distant depth.
-	}
-	// Checks if the box from minimum to maximum in object space is fully occluded when seen by the camera
-	// Must be the same camera as when occluders filled the grid with occlusion depth
-	bool isBoxOccluded(const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) {
-		if (!this->receiving) {
-			throwError("Cannot call renderer_isBoxVisible without first calling renderer_begin and giving occluder shapes to the pass!\n");
-		}
-		FVector3D corners[8];
-		GENERATE_BOX_CORNERS(corners, minimum, maximum)
-		ProjectedPoint projections[8];
-		return isHullOccluded(projections, corners, 8, modelToWorldTransform, camera);
+void model_render_threaded(const Model& model, const Transform3D &modelToWorldTransform, Renderer& renderer, const Camera &camera) {
+	MUST_EXIST(renderer, renderer_giveTask);
+	// Skip rendering if we do not have any model.
+	if (model.isNull()) {
+		return;
 	}
 	}
-	void giveTask(const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
-		if (!this->receiving) {
+	// Check the renderer's state.
+	#ifndef NDEBUG
+		if (!renderer_takesTriangles(renderer)) {
 			throwError("Cannot call renderer_giveTask before renderer_begin!\n");
 			throwError("Cannot call renderer_giveTask before renderer_begin!\n");
 		}
 		}
-		// If occluders are present, check if the model's bound is visible
-		if (this->occluded) {
-			FVector3D minimum, maximum;
-			model_getBoundingBox(model, minimum, maximum);
-			if (isBoxOccluded(minimum, maximum, modelToWorldTransform, camera)) {
-				// Skip projection of triangles if the whole bounding box is already behind occluders
-				return;
-			}
-		}
-		// TODO: Make an algorithm for selecting if the model should be queued as an instance or triangulated at once
-		//       An extra argument may choose to force an instance directly into the command queue
-		//           Because the model is being borrowed for vertex animation
-		//           To prevent the command queue from getting full hold as much as possible in a sorted list of instances
-		//           When the command queue is full, the solid instances will be drawn front to back before filtered is drawn back to front
-		model->render(&this->commandQueue, image_exists(this->colorBuffer) ? &(this->colorBuffer) : nullptr, image_exists(this->depthBuffer) ? &(this->depthBuffer) : nullptr, modelToWorldTransform, camera);
+	#endif
+	// Culling.
+	if (!camera.isBoxSeen(model->minBound, model->maxBound, modelToWorldTransform)) return;
+	// Occlusion.
+	if (renderer_hasOccluders(renderer)) {
+		FVector3D minimum, maximum;
+		model_getBoundingBox(model, minimum, maximum);
+		if (!renderer_isBoxVisible(renderer, minimum, maximum, modelToWorldTransform, camera)) return;
 	}
 	}
-	void endFrame(bool debugWireframe) {
-		if (!this->receiving) {
-			throwError("Called renderer_end without renderer_begin!\n");
-		}
-		this->receiving = false;
-		// Mark occluded triangles to prevent them from being rendered
-		completeOcclusion();
-		this->commandQueue.execute(IRect::FromSize(this->width, this->height));
-		if (image_exists(this->colorBuffer)) {
-			// Debug drawn triangles
-			if (debugWireframe) {
-				/*if (image_exists(this->depthGrid)) {
-					for (int cellY = 0; cellY < this->gridHeight; cellY++) {
-						for (int cellX = 0; cellX < this->gridWidth; cellX++) {
-							float depth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
-							if (depth < std::numeric_limits<float>::infinity()) {
-								int intensity = depth;
-								draw_rectangle(this->colorBuffer, IRect(cellX * cellSize + 4, cellY * cellSize + 4, cellSize - 8, cellSize - 8), ColorRgbaI32(intensity, intensity, 0, 255));
-							}
-						}
-					}
-				}*/
-				for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
-					if (!this->commandQueue.buffer[t].occluded) {
-						ITriangle2D *triangle = &(this->commandQueue.buffer[t].triangle);
-						draw_line(this->colorBuffer,
-						  triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
-						  triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
-						  ColorRgbaI32(255, 255, 255, 255)
-						);
-						draw_line(this->colorBuffer,
-						  triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
-						  triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
-						  ColorRgbaI32(255, 255, 255, 255)
-						);
-						draw_line(this->colorBuffer,
-						  triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
-						  triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
-						  ColorRgbaI32(255, 255, 255, 255)
-						);
-					}
-				}
-			}
-			// Debug anything else added to debugLines
-			for (int l = 0; l < this->debugLines.length(); l++) {
-				draw_line(this->colorBuffer, this->debugLines[l].x1, this->debugLines[l].y1, this->debugLines[l].x2, this->debugLines[l].y2, this->debugLines[l].color);
-			}
-			this->debugLines.clear();
-		}
-		this->commandQueue.clear();
+	// Render the model by calling the renderer API for each triangle.
+	// Get the filter.
+	Filter filter = model->filter;
+	// Transform and project all vertices.
+	int positionCount = model->positionBuffer.length();
+	VirtualStackAllocation<ProjectedPoint> projected(positionCount, "Projected points in renderer_giveTask");
+	for (int vert = 0; vert < positionCount; vert++) {
+		projected[vert] = camera.worldToScreen(modelToWorldTransform.transformPoint(model->positionBuffer[vert]));
 	}
 	}
-	void occludeFromTopRows(const Camera &camera) {
-		// Make sure that the depth grid exists with the correct dimensions.
-		this->prepareForOcclusion();
-		if (!this->receiving) {
-			throwError("Cannot call renderer_occludeFromTopRows without first calling renderer_begin!\n");
-		}
-		if (!image_exists(this->depthBuffer)) {
-			throwError("Cannot call renderer_occludeFromTopRows without having given a depth buffer in renderer_begin!\n");
-		}
-		SafePointer<float> depthRow = image_getSafePointer(this->depthBuffer);
-		int depthStride = image_getStride(this->depthBuffer);
-		SafePointer<float> gridRow = image_getSafePointer(this->depthGrid);
-		int gridStride = image_getStride(this->depthGrid);
-		if (camera.perspective) {
-			// Perspective case using 1/depth for the depth buffer.
-			for (int y = 0; y < this->height; y += cellSize) {
-				SafePointer<float> gridPixel = gridRow;
-				SafePointer<float> depthPixel = depthRow;
-				int x = 0;
-				int right = cellSize - 1;
-				float maxInvDistance;
-				// Scan bottom row of whole cell width
-				for (int gridX = 0; gridX < this->gridWidth; gridX++) {
-					maxInvDistance = std::numeric_limits<float>::infinity();
-					if (right >= this->width) { right = this->width; }
-					while (x < right) {
-						float newInvDistance = *depthPixel;
-						if (newInvDistance < maxInvDistance) { maxInvDistance = newInvDistance; }
-						depthPixel += 1;
-						x += 1;
-					}
-					float maxDistance = 1.0f / maxInvDistance;
-					float oldDistance = *gridPixel;
-					if (maxDistance < oldDistance) {
-						*gridPixel = maxDistance;
-					}
-					gridPixel += 1;
-					right += cellSize;
-				}
-				// Go to the next grid row
-				depthRow.increaseBytes(depthStride * cellSize);
-				gridRow.increaseBytes(gridStride);
-			}
-		} else {
-			// Orthogonal case where linear depth is used for both grid and depth buffer.
-			// 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.
-			for (int y = 0; y < this->height; y += cellSize) {
-				SafePointer<float> gridPixel = gridRow;
-				SafePointer<float> depthPixel = depthRow;
-				int x = 0;
-				int right = cellSize - 1;
-				float maxDistance;
-				// Scan bottom row of whole cell width
-				for (int gridX = 0; gridX < this->gridWidth; gridX++) {
-					maxDistance = 0.0f;
-					if (right >= this->width) { right = this->width; }
-					while (x < right) {
-						float newDistance = *depthPixel;
-						if (newDistance > maxDistance) { maxDistance = newDistance; }
-						depthPixel += 1;
-						x += 1;
-					}
-					float oldDistance = *gridPixel;
-					if (maxDistance < oldDistance) {
-						*gridPixel = maxDistance;
-					}
-					gridPixel += 1;
-					right += cellSize;
-				}
-				// Go to the next grid row
-				depthRow.increaseBytes(depthStride * cellSize);
-				gridRow.increaseBytes(gridStride);
+	for (int partIndex = 0; partIndex < model->partBuffer.length(); partIndex++) {
+		// Get a pointer to the current part.
+		Part *part = &(model->partBuffer[partIndex]);
+		// Get textures.
+		const TextureRgbaU8 diffuse = part->diffuseMap;
+		const TextureRgbaU8 light = part->lightMap;
+		for (int p = 0; p < part->polygonBuffer.length(); p++) {
+			Polygon polygon = part->polygonBuffer[p];
+			// Render first triangle in the polygon of indices 0, 1, 2.
+			renderer_giveTask_triangle(renderer,
+			  projected[polygon.pointIndices[0]],
+			  projected[polygon.pointIndices[1]],
+			  projected[polygon.pointIndices[2]],
+			  polygon.colors[0],
+			  polygon.colors[1],
+			  polygon.colors[2],
+			  polygon.texCoords[0],
+			  polygon.texCoords[1],
+			  polygon.texCoords[2],
+			  diffuse, light, filter, camera
+			);
+			if (polygon.pointIndices[3] != -1) {
+				// Render second triangle in the polygon of indices 0, 2, 3 to form a quad polygon.
+				renderer_giveTask_triangle(renderer,
+				  projected[polygon.pointIndices[0]],
+				  projected[polygon.pointIndices[2]],
+				  projected[polygon.pointIndices[3]],
+				  polygon.colors[0],
+				  polygon.colors[2],
+				  polygon.colors[3],
+				  polygon.texCoords[0],
+				  polygon.texCoords[2],
+				  polygon.texCoords[3],
+				  diffuse, light, filter, camera
+				);
 			}
 			}
 		}
 		}
 	}
 	}
-};
-
-Renderer renderer_create() {
-	return handle_create<RendererImpl>().setName("Renderer");
-}
-
-bool renderer_exists(const Renderer& renderer) {
-	return renderer.isNotNull();
-}
-
-void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
-	MUST_EXIST(renderer, renderer_begin);
-	renderer->beginFrame(colorBuffer, depthBuffer);
-}
-
-// TODO: Synchronous setting
-//       * Asynchronous (default)
-//         Only works on models that are locked from further editing
-//         Locked models can also be safely pooled for reuse then (ResourcePool)
-//       * Synced (for animation)
-//         Dispatch triangles directly to the command queue so that the current state of the model is captured
-//         This allow rendering many instances using the same model at different times
-//         Enabling vertex light, reflection maps and bone animation
-void renderer_giveTask(Renderer& renderer, const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
-	MUST_EXIST(renderer, renderer_giveTask);
-	if (model.isNotNull()) {
-		renderer->giveTask(model, modelToWorldTransform, camera);
-	}
 }
 }
 
 
-void renderer_giveTask_triangle(Renderer& renderer,
-  const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
-  const FVector4D &colorA, const FVector4D &colorB, const FVector4D &colorC,
-  const FVector4D &texCoordA, const FVector4D &texCoordB, const FVector4D &texCoordC,
-  const TextureRgbaU8& diffuseMap, const TextureRgbaU8& lightMap,
-  Filter filter, const Camera &camera) {
-	#ifndef NDEBUG
-		MUST_EXIST(renderer, renderer_addTriangle);
-	#endif
-	renderTriangleFromData(
-	  &(renderer->commandQueue), &(renderer->colorBuffer), &(renderer->depthBuffer), camera,
-	  posA, posB, posC,
-	  filter, &(diffuseMap), &(lightMap),
-	  TriangleTexCoords(texCoordA, texCoordB, texCoordC),
-	  TriangleColors(colorA, colorB, colorC)
-	);
 }
 }
-
-void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
-	MUST_EXIST(renderer, renderer_occludeFromBox);
-	renderer->occludeFromBox(minimum, maximum, modelToWorldTransform, camera, debugSilhouette);
-}
-
-void renderer_occludeFromExistingTriangles(Renderer& renderer) {
-	MUST_EXIST(renderer, renderer_optimize);
-	renderer->occludeFromExistingTriangles();
-}
-
-void renderer_occludeFromTopRows(Renderer& renderer, const Camera &camera) {
-	MUST_EXIST(renderer, renderer_occludeFromTopRows);
-	renderer->occludeFromTopRows(camera);
-}
-
-bool renderer_isBoxVisible(Renderer& renderer, const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) {
-	MUST_EXIST(renderer, renderer_isBoxVisible);
-	return !(renderer->isBoxOccluded(minimum, maximum, modelToWorldTransform, camera));
-}
-
-void renderer_end(Renderer& renderer, bool debugWireframe) {
-	MUST_EXIST(renderer, renderer_end);
-	renderer->endFrame(debugWireframe);
-}
-
-}
-

+ 14 - 91
Source/DFPSR/api/modelAPI.h

@@ -21,28 +21,25 @@
 //    3. This notice may not be removed or altered from any source
 //    3. This notice may not be removed or altered from any source
 //    distribution.
 //    distribution.
 
 
+// A reference API for 3D models, designed to make it easy to get started with 3D rendering.
+// Once you are too limited by the model API, you can call the renderer API directly with triangles from your own representation.
+
 #ifndef DFPSR_API_MODEL
 #ifndef DFPSR_API_MODEL
 #define DFPSR_API_MODEL
 #define DFPSR_API_MODEL
 
 
-#include "../math/FVector.h"
-#include "../implementation/image/Texture.h"
-#include "../implementation/render/Camera.h"
-#include "../implementation/render/ResourcePool.h"
+#include "rendererAPI.h"
 
 
-// TODO: Create a folder with types.
 namespace dsr {
 namespace dsr {
 	// A handle to a model.
 	// A handle to a model.
 	class ModelImpl;
 	class ModelImpl;
 	using Model = Handle<ModelImpl>;
 	using Model = Handle<ModelImpl>;
 }
 }
 
 
+// This header depends on defining Model ahead of including it to avoid cyclic dependencies.
 #include "../implementation/render/model/format/dmf1.h"
 #include "../implementation/render/model/format/dmf1.h"
 
 
+// TODO: Create a folder with types.
 namespace dsr {
 namespace dsr {
-	// A handle to a multi-threaded rendering context.
-	struct RendererImpl;
-	using Renderer = Handle<RendererImpl>;
-
 	// Normalized texture coordinates:
 	// Normalized texture coordinates:
 	//   (0.0f, 0.0f) is the texture coordinate for the upper left corner of the upper left pixel in the 2D texture.
 	//   (0.0f, 0.0f) is the texture coordinate for the upper left corner of the upper left pixel in the 2D texture.
 	//   (1.0f, 0.0f) is the texture coordinate for the upper right corner of the upper right pixel in the 2D texture.
 	//   (1.0f, 0.0f) is the texture coordinate for the upper right corner of the upper right pixel in the 2D texture.
@@ -280,7 +277,7 @@ namespace dsr {
 	// If this does not hold true then there is either an exception missing
 	// If this does not hold true then there is either an exception missing
 	// or a bug in the renderer, which should be reported as soon as possible.
 	// or a bug in the renderer, which should be reported as soon as possible.
 
 
-	// Single-threaded rendering (Slow but easy to use for small tasks)
+	// Single-threaded rendering (Easy to use directly, ideal for rendering in background threads)
 	//   Can be executed on different threads if targetImage and depthBuffer doesn't have overlapping memory lines between the threads
 	//   Can be executed on different threads if targetImage and depthBuffer doesn't have overlapping memory lines between the threads
 	// Pre-condition: colorBuffer and depthBuffer must have the same dimensions.
 	// Pre-condition: colorBuffer and depthBuffer must have the same dimensions.
 	// Side-effect: Render any model transformed by modelToWorldTransform, seen from camera, to any colorBuffer using any depthBuffer.
 	// Side-effect: Render any model transformed by modelToWorldTransform, seen from camera, to any colorBuffer using any depthBuffer.
@@ -292,90 +289,16 @@ namespace dsr {
 	// Side-effect: Render any model transformed by modelToWorldTransform, seen from camera, to any depthBuffer.
 	// Side-effect: Render any model transformed by modelToWorldTransform, seen from camera, to any depthBuffer.
 	//   An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
 	//   An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
 	void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera);
 	void model_renderDepth(const Model& model, const Transform3D &modelToWorldTransform, ImageF32& depthBuffer, const Camera &camera);
-
-	// Multi-threaded rendering (Huge performance boost with more CPU cores!)
-	// Post-condition: Returns the handle to a new multi-threaded rendering context.
-	//   It is basically a list of triangles to be drawn in parallel using a single call.
-	//   After creating a renderer, you may execute a number of batches using it.
-	//   Each batch may execute a number of tasks in parallel.
-	//   Call pattern:
-	//     renderer_create (renderer_begin renderer_giveTask* renderer_end)*
-	Renderer renderer_create();
-	// Post-condition: Returns true iff the renderer exists.
-	bool renderer_exists(const Renderer& renderer);
-	// Prepares for rendering by giving the target images to draw pixels on.
-	// This step makes sure that nobody changes the target dimensions while rendering,
-	// which could otherwise happen if someone requests a new canvas too often.
-	// Pre-condition:
-	//   renderer must refer to an existing renderer.
-	//   colorBuffer and depthBuffer must have the same dimensions.
-	void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer);
-	// Project an occluding box against the occlusion grid so that triangles hidden behind it will not be drawn.
-	// Occluders may only be placed within solid geometry, because otherwise it may affect the visual result.
-	// Should ideally be used before giving render tasks, so that optimizations can take advantage of early occlusion checks.
-	void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette = false);
-	// If you have drawn the ground in a separate pass and know that lower pixels along the current depth buffer are never further away from the camera,
-	// you can fill the occlusion grid using the furthest distance in the top row of each cell sampled from the depth buffer and know the maximum distance of each cell for occluding models in the next pass.
-	// Make sure to call it after renderer_begin (so that you don't clear your result on start), but before renderer_giveTask (so that whole models can be occluded without filling the buffer with projected triangles).
-	// Pre-condition:
-	//   The renderer must have started a pass with a depth buffer using renderer_begin.
-	void renderer_occludeFromTopRows(Renderer& renderer, const Camera &camera);
-	// After having filled the occlusion grid (using renderer_occludeFromBox, renderer_occludeFromTopRows or renderer_occludeFromExistingTriangles), you can check if a bounding box is visible.
-	//   For a single model, you can use model_getBoundingBox to get the local bound and then provide its model to world transform that would be used to render the specific instance.
-	//   This is already applied automatically in renderer_giveTask, but you might want to know which model may potentially be visible ahead of time
-	//   to bake effects into textures, procedurally generate geometry, skip whole groups of models in a broad-phase or use your own custom rasterizer.
-	// Opposite to when filling the occlusion grid, the tested bound must include the whole drawn content.
-	//   This makes sure that renderer_isBoxVisible will only return false if it cannot be seen, with exception for near clipping and abused occluders.
-	//   False positives from having the bounding box seen is to be expected, because the purpose is to save time by doing less work.
-	bool renderer_isBoxVisible(Renderer& renderer, const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera);
-	// Once an object passed game-specific occlusion tests, give it to the renderer using renderer_giveTask.
-	// The render job will be performed during the next call to renderer_end.
+	// Multi-threader rendering (Splits the target rows into separate threads for faster rendering)
+	// The render job will be performed during the next call to renderer_end with the same renderer.
 	// Pre-condition: renderer must refer to an existing renderer.
 	// Pre-condition: renderer must refer to an existing renderer.
 	// An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
 	// An empty model handle will be skipped silently, which can be used instead of an model with zero polygons.
 	// Side-effect: The visible triangles are queued up in the renderer.
 	// Side-effect: The visible triangles are queued up in the renderer.
-	void renderer_giveTask(Renderer& renderer, const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera);
-	// A move powerful alternative to renderer_giveTask, sending one triangle at a time without occlusion tests.
-	//   Call renderer_isBoxVisible for the whole model's bounding box to check if the triangles in your own representation should be drawn.
-	// Useful for engine specific model formats allowing vertex animation, vertex shading and texture shading.
-	//   Positions can be transformed to implement bone animation, or interpolated from key frames for vertex animation.
-	//   Vertex colors can be modified to implement dynamic vertex light, which is useful for animated geometry.
-	//   Having one texture per instances using the same geometry, makes it easy to apply shading in texture space for sub-surface scattering and soft shadows.
-	//     Simply transform each light source into object space and generate a normal map in object space instead of tangent space, to make fast texture space shading of rigid models.
-	// Side-effect:
-	//   Adds the triangle to the renderer's list of things to do when multi-threaded rasterization starts.
-	//   Vertex data is cloned by value and you may therefore generate vertex data dynamically and reuse buffers for multiple instances.
-	//   Textures are however taken as raw pointers.
-	//     Reference counting shared resources with multi-threading would be super slow and most textures are loaded from pools anyway.
-	//     Cloning whole textures would always be slower and take more memory than just storing one texture for each variation of shading.
-	//     Just don't delete the last handle to a texture while it is being rendered using multiple threads, and you get decent performance without crashes.
-	// Inputs:
-	//   The renderer must exist, because otherwise it does not know where to draw the result.
-	//     Safety checks are only performed in debug mode, so that rendering of triangles will not be slowed down too much in the final release.
-	//   posA, posB and posC are pre-projected screen coordinates containing camera space coordinates for clipping.
-	//     These are supposed to be projected once per position using the camera and then reused for all vertices that share the position.
-	//   By rounding projected coordinates to sub-pixel integers in advance, the rasterization algorithm can perform exact comparisons along the line between two triangles.
-	//     This guarantees that triangles that are not clipped against the view frustum will not leak pixels between triangles who had two share two projected positions.
-	//   colorA, colorB and colorC are the vertex colors.
-	//     If assigned to nearly identical values, a faster shader will be used to fill everything in a solid color.
-	//   texCoordA, texCoordB and texCoordC are the texture coordinates.
-	//     x and y elements contain UV1 for the diffuse map.
-	//     z and w elements contain UV2 for the light map.
-	//   Both diffuseMap and lightMap must be a valid texture or not exist.
-	//   See model_setFilter for an explanation of the available filters.
-	//   The camera should be the same that was used for projecting posA, posB and posC, so that new vertices from clipping can be projected again.
-	void renderer_giveTask_triangle(Renderer& renderer,
-	  const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
-	  const FVector4D &colorA, const FVector4D &colorB, const FVector4D &colorC,
-	  const FVector4D &texCoordA, const FVector4D &texCoordB, const FVector4D &texCoordC,
-	  const TextureRgbaU8& diffuseMap, const TextureRgbaU8& lightMap,
-	  Filter filter, const Camera &camera);
-	// Use already given triangles as occluders.
-	//   Used after calls to renderer_giveTask have filled the buffer with triangles, but before they are drawn using renderer_end.
-	void renderer_occludeFromExistingTriangles(Renderer& renderer);
-	// Side-effect: Finishes all the jobs in the rendering context so that triangles are rasterized to the targets given to renderer_begin.
-	// Pre-condition: renderer must refer to an existing renderer.
-	// If debugWireframe is true, each triangle's edges will be drawn on top of the drawn world to indicate how well the occlusion system is working
-	void renderer_end(Renderer& renderer, bool debugWireframe = false);
+	void model_render_threaded(const Model& model, const Transform3D &modelToWorldTransform, Renderer& renderer, const Camera &camera);
+	// Extending the renderer API with an alias for model_render_threaded with different argument order.
+	static inline void renderer_giveTask(Renderer& renderer, const Model& model, const Transform3D &modelToWorldTransform, const Camera &camera) {
+		model_render_threaded(model, modelToWorldTransform, renderer, camera);
+	}
 
 
 	// Imports a DMF model from file content.
 	// Imports a DMF model from file content.
 	//   Use in combination with string_load or your own system for storing files.
 	//   Use in combination with string_load or your own system for storing files.

+ 565 - 0
Source/DFPSR/api/rendererAPI.cpp

@@ -0,0 +1,565 @@
+// zlib open source license
+//
+// Copyright (c) 2019 to 2025 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+#define DSR_INTERNAL_ACCESS
+
+#include "rendererAPI.h"
+#include "imageAPI.h"
+#include "drawAPI.h"
+#include "../implementation/render/renderCore.h"
+#include "../base/virtualStack.h"
+#include <limits>
+
+#define MUST_EXIST(OBJECT, METHOD) if (OBJECT.isNull()) { throwError("The " #OBJECT " handle was null in " #METHOD "\n"); }
+
+namespace dsr {
+
+static const int cellSize = 16;
+
+static bool counterClockwise(const ProjectedPoint& p, const ProjectedPoint& q, const ProjectedPoint& r) {
+	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;
+}
+
+// outputHullCorners must be at least as big as inputHullCorners, so that it can hold the worst case output size.
+// Instead of not allowing less than three points, it copies the input as output when it happens to reduce pre-conditions.
+static void jarvisConvexHullAlgorithm(ProjectedPoint* outputHullCorners, int& outputCornerCount, const ProjectedPoint* inputHullCorners, int inputCornerCount) {
+	if (inputCornerCount < 3) {
+		outputCornerCount = inputCornerCount;
+		for (int p = 0; p < inputCornerCount; p++) {
+			outputHullCorners[p] = inputHullCorners[p];
+		}
+	} else {
+		int l = 0;
+		outputCornerCount = 0;
+		for (int i = 1; i < inputCornerCount; i++) {
+			if (inputHullCorners[i].flat.x < inputHullCorners[l].flat.x) {
+				l = i;
+			}
+		}
+		int p = l;
+		do {
+			if (outputCornerCount >= inputCornerCount) {
+				// Prevent getting stuck in an infinite loop from overflow
+				return;
+			}
+			outputHullCorners[outputCornerCount] = inputHullCorners[p]; outputCornerCount++;
+			int q = (p + 1) % inputCornerCount;
+			for (int i = 0; i < inputCornerCount; i++) {
+				if (counterClockwise(inputHullCorners[p], inputHullCorners[i], inputHullCorners[q])) {
+					q = i;
+				}
+			}
+			p = q;
+		} while (p != l);
+	}
+}
+
+// Transform and project the corners of a hull, so that the output can be given to the convex hull algorithm and used for occluding
+// Returns true if occluder culling passed, which may skip occluders that could have been visible
+static bool projectHull(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) {
+	for (int p = 0; p < cornerCount; p++) {
+		FVector3D worldPoint = modelToWorldTransform.transformPoint(inputHullCorners[p]);
+		FVector3D cameraPoint = camera.worldToCamera(worldPoint);
+		FVector3D narrowPoint = cameraPoint * FVector3D(0.5f, 0.5f, 1.0f);
+		for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
+			FPlane3D plane = camera.cullFrustum.getPlane(s);
+			if (!plane.inside(narrowPoint)) {
+				return false;
+			}
+		}
+		outputHullCorners[p] = camera.cameraToScreen(cameraPoint);
+	}
+	return true;
+}
+
+static IRect getPixelBoundFromProjection(const ProjectedPoint* convexHullCorners, int cornerCount) {
+	IRect result = IRect(convexHullCorners[0].flat.x / constants::unitsPerPixel, convexHullCorners[0].flat.y / constants::unitsPerPixel, 1, 1);
+	for (int p = 1; p < cornerCount; p++) {
+		result = IRect::merge(result, IRect(convexHullCorners[p].flat.x / constants::unitsPerPixel, convexHullCorners[p].flat.y / constants::unitsPerPixel, 1, 1));
+	}
+	return result;
+}
+
+static bool pointInsideOfEdge(const LVector2D &edgeA, const LVector2D &edgeB, const LVector2D &point) {
+	LVector2D edgeDirection = LVector2D(edgeB.y - edgeA.y, edgeA.x - edgeB.x);
+	LVector2D relativePosition = point - edgeA;
+	return (edgeDirection.x * relativePosition.x) + (edgeDirection.y * relativePosition.y) <= 0;
+}
+
+// Returns true iff the point is inside of the hull
+// convexHullCorners from 0 to cornerCount-1 must be sorted clockwise and may not include any concave corners
+static bool pointInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const LVector2D &point) {
+	for (int c = 0; c < cornerCount; c++) {
+		int nc = c + 1;
+		if (nc == cornerCount) {
+			nc = 0;
+		}
+		if (!pointInsideOfEdge(convexHullCorners[c].flat, convexHullCorners[nc].flat, point)) {
+			// Outside of one edge, not inside
+			return false;
+		}
+	}
+	// Passed all edge tests
+	return true;
+}
+
+// Returns true iff all corners of the rectangle are inside of the hull
+static bool rectangleInsideOfHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect &rectangle) {
+	return pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.top()))
+		&& pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.top()))
+		&& pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.left(), rectangle.bottom()))
+		&& pointInsideOfHull(convexHullCorners, cornerCount, LVector2D(rectangle.right(), rectangle.bottom()));
+}
+
+struct DebugLine {
+	int64_t x1, y1, x2, y2;
+	ColorRgbaI32 color;
+	DebugLine(int64_t x1, int64_t y1, int64_t x2, int64_t y2, const ColorRgbaI32& color)
+	: x1(x1), y1(y1), x2(x2), y2(y2), color(color) {}
+};
+
+// Context for multi-threaded rendering of triangles in a command queue
+struct RendererImpl {
+	bool receiving = false; // Preventing version dependency by only allowing calls in the expected order
+	ImageRgbaU8 colorBuffer; // The color image being rendered to
+	ImageF32 depthBuffer; // Linear depth for isometric cameras, 1 / depth for perspective cameras
+	ImageF32 depthGrid; // An occlusion grid of cellSize² cells representing the longest linear depth where something might be visible
+	CommandQueue commandQueue; // Triangles to be drawn
+	List<DebugLine> debugLines; // Additional lines to be drawn as an overlay for debugging occlusion
+	int width = 0, height = 0, gridWidth = 0, gridHeight = 0;
+	bool occluded = false;
+	RendererImpl() {}
+	void beginFrame(ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
+		if (this->receiving) {
+			throwError("Called renderer_begin on the same renderer twice without ending the previous batch!\n");
+		}
+		this->receiving = true;
+		this->colorBuffer = colorBuffer;
+		this->depthBuffer = depthBuffer;
+		if (image_exists(this->colorBuffer)) {
+			this->width = image_getWidth(this->colorBuffer);
+			this->height = image_getHeight(this->colorBuffer);
+		} else if (image_exists(this->depthBuffer)) {
+			this->width = image_getWidth(this->depthBuffer);
+			this->height = image_getHeight(this->depthBuffer);
+		}
+		this->gridWidth = (this->width + (cellSize - 1)) / cellSize;
+		this->gridHeight = (this->height + (cellSize - 1)) / cellSize;
+		this->occluded = false;
+	}
+	IRect getOuterCellBound(const IRect &pixelBound) const {
+		int minCellX = pixelBound.left() / cellSize;
+		int maxCellX = pixelBound.right() / cellSize + 1;
+		int minCellY = pixelBound.top() / cellSize;
+		int maxCellY = pixelBound.bottom() / cellSize + 1;
+		if (minCellX < 0) { minCellX = 0; }
+		if (minCellY < 0) { minCellY = 0; }
+		if (maxCellX > this->gridWidth) { maxCellX = this->gridWidth; }
+		if (maxCellY > this->gridHeight) { maxCellY = this->gridHeight; }
+		return IRect(minCellX, minCellY, maxCellX - minCellX, maxCellY - minCellY);
+	}
+	// Called before occluding so that the grid is initialized once when used and skipped when not used
+	void prepareForOcclusion() {
+		if (!this->occluded) {
+			// Allocate the grid if a sufficiently large one does not already exist
+			if (!(image_exists(this->depthGrid) && image_getWidth(this->depthGrid) >= gridWidth && image_getHeight(this->depthGrid) >= gridHeight)) {
+				this->depthGrid = image_create_F32(gridWidth, gridHeight);
+			}
+			// Use inifnite depth in camera space
+			image_fill(this->depthGrid, std::numeric_limits<float>::infinity());
+		}
+		this->occluded = true;
+	}
+	// If any occluder has been used during this pass, all triangles in the buffer will be filtered based using depthGrid
+	void completeOcclusion() {
+		if (this->occluded) {
+			for (int t = this->commandQueue.buffer.length() - 1; t >= 0; t--) {
+				bool anyVisible = false;
+				ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
+				IRect outerBound = getOuterCellBound(triangle.wholeBound);
+				for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
+					for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
+						// TODO: Optimize access using SafePointer iteration
+						float backgroundDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
+						float triangleDepth = triangle.position[0].cs.z;
+						replaceWithSmaller(triangleDepth, triangle.position[1].cs.z);
+						replaceWithSmaller(triangleDepth, triangle.position[2].cs.z);
+						if (triangleDepth < backgroundDepth + 0.001) {
+							anyVisible = true;
+						}
+					}
+				}
+				if (!anyVisible) {
+					// TODO: Make triangle swapping work so that the list can be sorted
+					this->commandQueue.buffer[t].occluded = true;
+				}
+			}
+		}
+	}
+	void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount, const IRect& pixelBound) {
+		// Loop over the outer bound
+		if (pixelBound.width() > cellSize && pixelBound.height() > cellSize) {
+			float distance = 0.0f;
+			for (int c = 0; c < cornerCount; c++) {
+				replaceWithLarger(distance, convexHullCorners[c].cs.z);
+			}
+			// Loop over all cells within the bound
+			IRect outerBound = getOuterCellBound(pixelBound);
+			for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
+				for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
+					IRect pixelRegion = IRect(cellX * cellSize, cellY * cellSize, cellSize, cellSize);
+					IRect subPixelRegion = pixelRegion * constants::unitsPerPixel;
+					if (rectangleInsideOfHull(convexHullCorners, cornerCount, subPixelRegion)) {
+						float oldDepth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
+						if (distance < oldDepth) {
+							image_writePixel(this->depthGrid, cellX, cellY, distance);
+						}
+					}
+				}
+			}
+		}
+	}
+	void occludeFromSortedHull(const ProjectedPoint* convexHullCorners, int cornerCount) {
+		occludeFromSortedHull(convexHullCorners, cornerCount, getPixelBoundFromProjection(convexHullCorners, cornerCount));
+	}
+	void occludeFromExistingTriangles() {
+		if (!this->receiving) {
+			throwError("Cannot call renderer_occludeFromExistingTriangles without first calling renderer_begin!\n");
+		}
+		prepareForOcclusion();
+		// Generate a depth grid to remove many small triangles behind larger triangles
+		//   This will leave triangles along seams but at least begin to remove the worst unwanted drawing
+		for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
+			// Get the current triangle from the queue
+			Filter filter = this->commandQueue.buffer[t].filter;
+			if (filter == Filter::Solid) {
+				ITriangle2D triangle = this->commandQueue.buffer[t].triangle;
+				occludeFromSortedHull(triangle.position, 3, triangle.wholeBound);
+			}
+		}
+	}
+	#define GENERATE_BOX_CORNERS(TARGET, MIN, MAX) \
+		TARGET[0] = FVector3D(MIN.x, MIN.y, MIN.z); \
+		TARGET[1] = FVector3D(MIN.x, MIN.y, MAX.z); \
+		TARGET[2] = FVector3D(MIN.x, MAX.y, MIN.z); \
+		TARGET[3] = FVector3D(MIN.x, MAX.y, MAX.z); \
+		TARGET[4] = FVector3D(MAX.x, MIN.y, MIN.z); \
+		TARGET[5] = FVector3D(MAX.x, MIN.y, MAX.z); \
+		TARGET[6] = FVector3D(MAX.x, MAX.y, MIN.z); \
+		TARGET[7] = FVector3D(MAX.x, MAX.y, MAX.z);
+	// Fills the occlusion grid using the box, so that things behind it can skip rendering
+	void occludeFromBox(const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
+		if (!this->receiving) {
+			throwError("Cannot call renderer_occludeFromBox without first calling renderer_begin!\n");
+		}
+		prepareForOcclusion();
+		static const int pointCount = 8;
+		FVector3D localPoints[pointCount];
+		ProjectedPoint projections[pointCount];
+		ProjectedPoint edgeCorners[pointCount];
+		GENERATE_BOX_CORNERS(localPoints, minimum, maximum)
+		if (projectHull(projections, localPoints, 8, modelToWorldTransform, camera)) {
+			// Get a 2D convex hull from the projected corners
+			int edgeCornerCount = 0;
+			jarvisConvexHullAlgorithm(edgeCorners, edgeCornerCount, projections, 8);
+			occludeFromSortedHull(edgeCorners, edgeCornerCount);
+			// Allow saving the 2D silhouette for debugging
+			if (debugSilhouette) {
+				for (int p = 0; p < edgeCornerCount; p++) {
+					int q = (p + 1) % edgeCornerCount;
+					if (projections[p].cs.z > camera.nearClip) {
+						this->debugLines.pushConstruct(
+						  edgeCorners[p].flat.x / constants::unitsPerPixel, edgeCorners[p].flat.y / constants::unitsPerPixel,
+						  edgeCorners[q].flat.x / constants::unitsPerPixel, edgeCorners[q].flat.y / constants::unitsPerPixel,
+						  ColorRgbaI32(0, 255, 255, 255)
+						);
+					}
+				}
+			}
+		}
+	}
+	// Occlusion test for whole model bounds.
+	// Returns false if the convex hull of the corners has a chance to be seen from the camera.
+	bool isHullOccluded(ProjectedPoint* outputHullCorners, const FVector3D* inputHullCorners, int cornerCount, const Transform3D &modelToWorldTransform, const Camera &camera) const {
+		VirtualStackAllocation<FVector3D> cameraPoints(cornerCount);
+		for (int p = 0; p < cornerCount; p++) {
+			cameraPoints[p] = camera.worldToCamera(modelToWorldTransform.transformPoint(inputHullCorners[p]));
+			outputHullCorners[p] = camera.cameraToScreen(cameraPoints[p]);
+		}
+		// Culling test to see if all points are outside of the same plane of the view frustum.
+		for (int s = 0; s < camera.cullFrustum.getPlaneCount(); s++) {
+			bool allOutside = true; // True until prooven false.
+			FPlane3D plane = camera.cullFrustum.getPlane(s);
+			for (int p = 0; p < cornerCount; p++) {
+				if (plane.inside(cameraPoints[p])) {
+					// One point was inside of this plane, so it can not guarantee that all interpolated points between the corners are outside.
+					allOutside = false;
+					break;
+				}
+			}
+			// If all points are outside of the same plane in the view frustum...
+			if (allOutside) {
+				// ...then we know that all interpolated points in between are also outside of this plane.
+				return true; // Occluded due to failing culling test.
+			}
+		}
+		IRect pixelBound = getPixelBoundFromProjection(outputHullCorners, cornerCount);
+		float closestDistance = std::numeric_limits<float>::infinity();
+		for (int c = 0; c < cornerCount; c++) {
+			replaceWithSmaller(closestDistance, outputHullCorners[c].cs.z);
+		}
+		// Loop over all cells within the bound
+		IRect outerBound = getOuterCellBound(pixelBound);
+		for (int cellY = outerBound.top(); cellY < outerBound.bottom(); cellY++) {
+			for (int cellX = outerBound.left(); cellX < outerBound.right(); cellX++) {
+				if (closestDistance < image_readPixel_clamp(this->depthGrid, cellX, cellY)) {
+					return false; // Visible because one cell had a more distant maximum depth.
+				}
+			}
+		}
+		return true; // Occluded, because none of the cells had a more distant depth.
+	}
+	// Checks if the box from minimum to maximum in object space is fully occluded when seen by the camera
+	// Must be the same camera as when occluders filled the grid with occlusion depth
+	bool isBoxOccluded(const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) const {
+		if (!this->receiving) {
+			throwError("Cannot call renderer_isBoxVisible without first calling renderer_begin and giving occluder shapes to the pass!\n");
+		}
+		FVector3D corners[8];
+		GENERATE_BOX_CORNERS(corners, minimum, maximum)
+		ProjectedPoint projections[8];
+		return isHullOccluded(projections, corners, 8, modelToWorldTransform, camera);
+	}
+	void endFrame(bool debugWireframe) {
+		if (!this->receiving) {
+			throwError("Called renderer_end without renderer_begin!\n");
+		}
+		this->receiving = false;
+		// Mark occluded triangles to prevent them from being rendered
+		completeOcclusion();
+		this->commandQueue.execute(IRect::FromSize(this->width, this->height));
+		if (image_exists(this->colorBuffer)) {
+			// Debug drawn triangles
+			if (debugWireframe) {
+				/*if (image_exists(this->depthGrid)) {
+					for (int cellY = 0; cellY < this->gridHeight; cellY++) {
+						for (int cellX = 0; cellX < this->gridWidth; cellX++) {
+							float depth = image_readPixel_clamp(this->depthGrid, cellX, cellY);
+							if (depth < std::numeric_limits<float>::infinity()) {
+								int intensity = depth;
+								draw_rectangle(this->colorBuffer, IRect(cellX * cellSize + 4, cellY * cellSize + 4, cellSize - 8, cellSize - 8), ColorRgbaI32(intensity, intensity, 0, 255));
+							}
+						}
+					}
+				}*/
+				for (int t = 0; t < this->commandQueue.buffer.length(); t++) {
+					if (!this->commandQueue.buffer[t].occluded) {
+						ITriangle2D *triangle = &(this->commandQueue.buffer[t].triangle);
+						draw_line(this->colorBuffer,
+						  triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
+						  triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
+						  ColorRgbaI32(255, 255, 255, 255)
+						);
+						draw_line(this->colorBuffer,
+						  triangle->position[1].flat.x / constants::unitsPerPixel, triangle->position[1].flat.y / constants::unitsPerPixel,
+						  triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
+						  ColorRgbaI32(255, 255, 255, 255)
+						);
+						draw_line(this->colorBuffer,
+						  triangle->position[2].flat.x / constants::unitsPerPixel, triangle->position[2].flat.y / constants::unitsPerPixel,
+						  triangle->position[0].flat.x / constants::unitsPerPixel, triangle->position[0].flat.y / constants::unitsPerPixel,
+						  ColorRgbaI32(255, 255, 255, 255)
+						);
+					}
+				}
+			}
+			// Debug anything else added to debugLines
+			for (int l = 0; l < this->debugLines.length(); l++) {
+				draw_line(this->colorBuffer, this->debugLines[l].x1, this->debugLines[l].y1, this->debugLines[l].x2, this->debugLines[l].y2, this->debugLines[l].color);
+			}
+			this->debugLines.clear();
+		}
+		this->commandQueue.clear();
+	}
+	void occludeFromTopRows(const Camera &camera) {
+		// Make sure that the depth grid exists with the correct dimensions.
+		this->prepareForOcclusion();
+		if (!this->receiving) {
+			throwError("Cannot call renderer_occludeFromTopRows without first calling renderer_begin!\n");
+		}
+		if (!image_exists(this->depthBuffer)) {
+			throwError("Cannot call renderer_occludeFromTopRows without having given a depth buffer in renderer_begin!\n");
+		}
+		SafePointer<float> depthRow = image_getSafePointer(this->depthBuffer);
+		int depthStride = image_getStride(this->depthBuffer);
+		SafePointer<float> gridRow = image_getSafePointer(this->depthGrid);
+		int gridStride = image_getStride(this->depthGrid);
+		if (camera.perspective) {
+			// Perspective case using 1/depth for the depth buffer.
+			for (int y = 0; y < this->height; y += cellSize) {
+				SafePointer<float> gridPixel = gridRow;
+				SafePointer<float> depthPixel = depthRow;
+				int x = 0;
+				int right = cellSize - 1;
+				float maxInvDistance;
+				// Scan bottom row of whole cell width
+				for (int gridX = 0; gridX < this->gridWidth; gridX++) {
+					maxInvDistance = std::numeric_limits<float>::infinity();
+					if (right >= this->width) { right = this->width; }
+					while (x < right) {
+						float newInvDistance = *depthPixel;
+						if (newInvDistance < maxInvDistance) { maxInvDistance = newInvDistance; }
+						depthPixel += 1;
+						x += 1;
+					}
+					float maxDistance = 1.0f / maxInvDistance;
+					float oldDistance = *gridPixel;
+					if (maxDistance < oldDistance) {
+						*gridPixel = maxDistance;
+					}
+					gridPixel += 1;
+					right += cellSize;
+				}
+				// Go to the next grid row
+				depthRow.increaseBytes(depthStride * cellSize);
+				gridRow.increaseBytes(gridStride);
+			}
+		} else {
+			// Orthogonal case where linear depth is used for both grid and depth buffer.
+			// 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.
+			for (int y = 0; y < this->height; y += cellSize) {
+				SafePointer<float> gridPixel = gridRow;
+				SafePointer<float> depthPixel = depthRow;
+				int x = 0;
+				int right = cellSize - 1;
+				float maxDistance;
+				// Scan bottom row of whole cell width
+				for (int gridX = 0; gridX < this->gridWidth; gridX++) {
+					maxDistance = 0.0f;
+					if (right >= this->width) { right = this->width; }
+					while (x < right) {
+						float newDistance = *depthPixel;
+						if (newDistance > maxDistance) { maxDistance = newDistance; }
+						depthPixel += 1;
+						x += 1;
+					}
+					float oldDistance = *gridPixel;
+					if (maxDistance < oldDistance) {
+						*gridPixel = maxDistance;
+					}
+					gridPixel += 1;
+					right += cellSize;
+				}
+				// Go to the next grid row
+				depthRow.increaseBytes(depthStride * cellSize);
+				gridRow.increaseBytes(gridStride);
+			}
+		}
+	}
+};
+
+ImageRgbaU8 renderer_getColorBuffer(const Renderer& renderer) {
+	MUST_EXIST(renderer, renderer_getColorBuffer);
+	return renderer->receiving ? renderer->colorBuffer : ImageRgbaU8();
+}
+
+ImageF32 renderer_getDepthBuffer(const Renderer& renderer) {
+	MUST_EXIST(renderer, renderer_getDepthBuffer);
+	return renderer->receiving ? renderer->depthBuffer : ImageF32();
+}
+
+Renderer renderer_create() {
+	return handle_create<RendererImpl>().setName("Renderer");
+}
+
+bool renderer_exists(const Renderer& renderer) {
+	return renderer.isNotNull();
+}
+
+void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer) {
+	MUST_EXIST(renderer, renderer_begin);
+	renderer->beginFrame(colorBuffer, depthBuffer);
+}
+
+void renderer_giveTask_triangle(Renderer& renderer,
+  const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
+  const FVector4D &colorA, const FVector4D &colorB, const FVector4D &colorC,
+  const FVector4D &texCoordA, const FVector4D &texCoordB, const FVector4D &texCoordC,
+  const TextureRgbaU8& diffuseMap, const TextureRgbaU8& lightMap,
+  Filter filter, const Camera &camera) {
+	#ifndef NDEBUG
+		MUST_EXIST(renderer, renderer_addTriangle);
+	#endif
+	renderTriangleFromData(
+	  &(renderer->commandQueue), renderer->colorBuffer, renderer->depthBuffer, camera,
+	  posA, posB, posC,
+	  filter, diffuseMap, lightMap,
+	  TriangleTexCoords(texCoordA, texCoordB, texCoordC),
+	  TriangleColors(colorA, colorB, colorC)
+	);
+}
+
+void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette) {
+	#ifndef NDEBUG
+		MUST_EXIST(renderer, renderer_occludeFromBox);
+	#endif
+	renderer->occludeFromBox(minimum, maximum, modelToWorldTransform, camera, debugSilhouette);
+}
+
+void renderer_occludeFromExistingTriangles(Renderer& renderer) {
+	MUST_EXIST(renderer, renderer_optimize);
+	renderer->occludeFromExistingTriangles();
+}
+
+void renderer_occludeFromTopRows(Renderer& renderer, const Camera &camera) {
+	MUST_EXIST(renderer, renderer_occludeFromTopRows);
+	renderer->occludeFromTopRows(camera);
+}
+
+bool renderer_isBoxVisible(const Renderer& renderer, const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera) {
+	#ifndef NDEBUG
+		MUST_EXIST(renderer, renderer_isBoxVisible);
+	#endif
+	return !(renderer->isBoxOccluded(minimum, maximum, modelToWorldTransform, camera));
+}
+
+void renderer_end(Renderer& renderer, bool debugWireframe) {
+	MUST_EXIST(renderer, renderer_end);
+	renderer->endFrame(debugWireframe);
+}
+
+bool renderer_takesTriangles(const Renderer& renderer) {
+	#ifndef NDEBUG
+		MUST_EXIST(renderer, renderer_isReceivingTriangles);
+	#endif
+	return renderer->receiving;
+}
+
+bool renderer_hasOccluders(const Renderer& renderer) {
+	#ifndef NDEBUG
+		MUST_EXIST(renderer, renderer_hasOccluders);
+	#endif
+	return renderer->occluded;
+}
+
+}

+ 138 - 0
Source/DFPSR/api/rendererAPI.h

@@ -0,0 +1,138 @@
+// zlib open source license
+//
+// Copyright (c) 2018 to 2025 David Forsgren Piuva
+// 
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+// 
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+// 
+//    1. The origin of this software must not be misrepresented; you must not
+//    claim that you wrote the original software. If you use this software
+//    in a product, an acknowledgment in the product documentation would be
+//    appreciated but is not required.
+// 
+//    2. Altered source versions must be plainly marked as such, and must not be
+//    misrepresented as being the original software.
+// 
+//    3. This notice may not be removed or altered from any source
+//    distribution.
+
+// TODO: Also provide single-threaded rendering using a similar interface.
+
+// An API for multi-threaded rendering of triangles.
+// Optimized for a few large triangles with textures, which means high overhead per triangle but low overhead per pixel.
+//   * Performs triangle clipping on triangles.
+//     If slightly outside, the rasterizer will clip the triangle after projection without creating any holes between edges.
+//     If far outside, the triangle will be subdivided into multiple triangles using floating-point operations to prevent integer overflow.
+//     Avoid triangles that become very large after projection if you want to completely avoid floating-point triangle clipping.
+//     Smaller triangles also have a higher chance of being occluded by your shapes.
+//   * It perspective correction per pixel to make rendering accurate at the cost of performance.
+
+#ifndef DFPSR_API_RENDERER
+#define DFPSR_API_RENDERER
+
+#include "../math/FVector.h"
+#include "../implementation/image/Texture.h"
+#include "../implementation/render/Camera.h"
+#include "../implementation/render/ResourcePool.h"
+
+namespace dsr {
+
+	// A handle to a multi-threaded rendering context.
+	struct RendererImpl;
+	using Renderer = Handle<RendererImpl>;
+
+	// Multi-threaded rendering (Huge performance boost with more CPU cores!)
+	// Post-condition: Returns the handle to a new multi-threaded rendering context.
+	//   It is basically a list of triangles to be drawn in parallel using a single call.
+	//   After creating a renderer, you may execute a number of batches using it.
+	//   Each batch may execute a number of tasks in parallel.
+	//   Call pattern:
+	//     renderer_create (renderer_begin renderer_giveTask* renderer_end)*
+	Renderer renderer_create();
+	// Post-condition: Returns true iff the renderer exists.
+	bool renderer_exists(const Renderer& renderer);
+	// Prepares for rendering by giving the target images to draw pixels on.
+	// This step makes sure that nobody changes the target dimensions while rendering,
+	// which could otherwise happen if someone requests a new canvas too often.
+	// Pre-condition:
+	//   renderer must refer to an existing renderer.
+	//   colorBuffer and depthBuffer must have the same dimensions.
+	void renderer_begin(Renderer& renderer, ImageRgbaU8& colorBuffer, ImageF32& depthBuffer);
+	// Pre-condition: Renderer must exist.
+	// Post-condition: Returns the color buffer given to renderer_begin, or an empty image handle if not rendering.
+	ImageRgbaU8 renderer_getColorBuffer(const Renderer& renderer);
+	// Pre-condition: Renderer must exist.
+	// Post-condition: Returns the depth buffer given to renderer_begin, or an empty image handle if not rendering.
+	ImageF32 renderer_getDepthBuffer(const Renderer& renderer);
+	// Returns true between renderer_begin and renderer_end when triangles can be sent to the renderer.
+	bool renderer_takesTriangles(const Renderer& renderer);
+	// Project an occluding box against the occlusion grid so that triangles hidden behind it will not be drawn.
+	// Occluders may only be placed within solid geometry, because otherwise it may affect the visual result.
+	// Should ideally be used before giving render tasks, so that optimizations can take advantage of early occlusion checks.
+	void renderer_occludeFromBox(Renderer& renderer, const FVector3D& minimum, const FVector3D& maximum, const Transform3D &modelToWorldTransform, const Camera &camera, bool debugSilhouette = false);
+	// If you have drawn the ground in a separate pass and know that lower pixels along the current depth buffer are never further away from the camera,
+	// you can fill the occlusion grid using the furthest distance in the top row of each cell sampled from the depth buffer and know the maximum distance of each cell for occluding models in the next pass.
+	// Make sure to call it after renderer_begin (so that you don't clear your result on start), but before checking bounding box occlusion and sending triangles to draw.
+	// Pre-condition:
+	//   The renderer must have started a pass with a depth buffer using renderer_begin.
+	void renderer_occludeFromTopRows(Renderer& renderer, const Camera &camera);
+	// Returns true if the renderer contains any occluders.
+	bool renderer_hasOccluders(const Renderer& renderer);
+	// After having filled the occlusion grid (using renderer_occludeFromBox, renderer_occludeFromTopRows or renderer_occludeFromExistingTriangles), you can check if a bounding box is visible.
+	//   For a single model, you can use model_getBoundingBox to get the local bound and then provide its model to world transform that would be used to render the specific instance.
+	//   This is already applied automatically in renderer_giveTask, but you might want to know which model may potentially be visible ahead of time
+	//   to bake effects into textures, procedurally generate geometry, skip whole groups of models in a broad-phase or use your own custom rasterizer.
+	// Opposite to when filling the occlusion grid, the tested bound must include the whole drawn content.
+	//   This makes sure that renderer_isBoxVisible will only return false if it cannot be seen, with exception for near clipping and abused occluders.
+	//   False positives from having the bounding box seen is to be expected, because the purpose is to save time by doing less work.
+	bool renderer_isBoxVisible(const Renderer& renderer, const FVector3D &minimum, const FVector3D &maximum, const Transform3D &modelToWorldTransform, const Camera &camera);
+	// A move powerful alternative to renderer_giveTask, sending one triangle at a time without occlusion tests.
+	//   Call renderer_isBoxVisible for the whole model's bounding box to check if the triangles in your own representation should be drawn.
+	// Useful for engine specific model formats allowing vertex animation, vertex shading and texture shading.
+	//   Positions can be transformed to implement bone animation, or interpolated from key frames for vertex animation.
+	//   Vertex colors can be modified to implement dynamic vertex light, which is useful for animated geometry.
+	//   Having one texture per instances using the same geometry, makes it easy to apply shading in texture space for sub-surface scattering and soft shadows.
+	//     Simply transform each light source into object space and generate a normal map in object space instead of tangent space, to make fast texture space shading of rigid models.
+	// Side-effect:
+	//   Adds the triangle to the renderer's list of things to do when multi-threaded rasterization starts.
+	//   Vertex data is cloned by value and you may therefore generate vertex data dynamically and reuse buffers for multiple instances.
+	//   Textures are however taken as raw pointers.
+	//     Reference counting shared resources with multi-threading would be super slow and most textures are loaded from pools anyway.
+	//     Cloning whole textures would always be slower and take more memory than just storing one texture for each variation of shading.
+	//     Just don't delete the last handle to a texture while it is being rendered using multiple threads, and you get decent performance without crashes.
+	// Inputs:
+	//   The renderer must exist, because otherwise it does not know where to draw the result.
+	//     Safety checks are only performed in debug mode, so that rendering of triangles will not be slowed down too much in the final release.
+	//   posA, posB and posC are pre-projected screen coordinates containing camera space coordinates for clipping.
+	//     These are supposed to be projected once per position using the camera and then reused for all vertices that share the position.
+	//   By rounding projected coordinates to sub-pixel integers in advance, the rasterization algorithm can perform exact comparisons along the line between two triangles.
+	//     This guarantees that triangles that are not clipped against the view frustum will not leak pixels between triangles who had two share two projected positions.
+	//   colorA, colorB and colorC are the vertex colors.
+	//     If assigned to nearly identical values, a faster shader will be used to fill everything in a solid color.
+	//   texCoordA, texCoordB and texCoordC are the texture coordinates.
+	//     x and y elements contain UV1 for the diffuse map.
+	//     z and w elements contain UV2 for the light map.
+	//   Both diffuseMap and lightMap must be a valid texture or not exist.
+	//   See model_setFilter for an explanation of the available filters.
+	//   The camera should be the same that was used for projecting posA, posB and posC, so that new vertices from clipping can be projected again.
+	void renderer_giveTask_triangle(Renderer& renderer,
+	  const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
+	  const FVector4D &colorA, const FVector4D &colorB, const FVector4D &colorC,
+	  const FVector4D &texCoordA, const FVector4D &texCoordB, const FVector4D &texCoordC,
+	  const TextureRgbaU8& diffuseMap, const TextureRgbaU8& lightMap,
+	  Filter filter, const Camera &camera);
+	// Use already given triangles as occluders.
+	//   Used after calls to renderer_giveTask have filled the buffer with triangles, but before they are drawn using renderer_end.
+	void renderer_occludeFromExistingTriangles(Renderer& renderer);
+	// Side-effect: Finishes all the jobs in the rendering context so that triangles are rasterized to the targets given to renderer_begin.
+	// Pre-condition: renderer must refer to an existing renderer.
+	// If debugWireframe is true, each triangle's edges will be drawn on top of the drawn world to indicate how well the occlusion system is working
+	void renderer_end(Renderer& renderer, bool debugWireframe = false);
+}
+
+#endif

+ 8 - 11
Source/DFPSR/implementation/render/model/Model.cpp

@@ -132,7 +132,7 @@ int Part::getPolygonVertexCount(int polygonIndex) const {
 //         Only decreasing the length of the point buffer, changing a position index or adding new polygons should set it to false
 //         Only decreasing the length of the point buffer, changing a position index or adding new polygons should set it to false
 //         Only running validation before rendering should set it from false to true
 //         Only running validation before rendering should set it from false to true
 //   point indices may not go outside of projected's array range
 //   point indices may not go outside of projected's array range
-static void renderTriangleFromPolygon(CommandQueue *commandQueue, ImageRgbaU8 *targetImage, ImageF32 *depthBuffer, const Camera &camera, const Polygon &polygon, int triangleIndex, const ProjectedPoint *projected, Filter filter, const TextureRgbaU8 *diffuse, const TextureRgbaU8 *light) {
+static void renderTriangleFromPolygon(CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer, const Camera &camera, const Polygon &polygon, int triangleIndex, const ProjectedPoint *projected, Filter filter, const TextureRgbaU8 &diffuse, const TextureRgbaU8 &light) {
 	// Triangle fan starting from the first vertex of the polygon
 	// Triangle fan starting from the first vertex of the polygon
 	int indexA = 0;
 	int indexA = 0;
 	int indexB = 1 + triangleIndex;
 	int indexB = 1 + triangleIndex;
@@ -147,24 +147,21 @@ static void renderTriangleFromPolygon(CommandQueue *commandQueue, ImageRgbaU8 *t
 	renderTriangleFromData(commandQueue, targetImage, depthBuffer, camera, posA, posB, posC, filter, diffuse, light, texCoords, colors);
 	renderTriangleFromData(commandQueue, targetImage, depthBuffer, camera, posA, posB, posC, filter, diffuse, light, texCoords, colors);
 }
 }
 
 
-void Part::render(CommandQueue *commandQueue, ImageRgbaU8* targetImage, ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, Filter filter, const ProjectedPoint* projected) const {
-	// Get textures
-	const TextureRgbaU8 *diffuse = &(this->diffuseMap);
-	const TextureRgbaU8 *light = &(this->lightMap);
+void Part::render(CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, Filter filter, const ProjectedPoint* projected) const {
 	for (int p = 0; p < this->polygonBuffer.length(); p++) {
 	for (int p = 0; p < this->polygonBuffer.length(); p++) {
 		Polygon polygon = this->polygonBuffer[p];
 		Polygon polygon = this->polygonBuffer[p];
 		if (polygon.pointIndices[3] == -1) {
 		if (polygon.pointIndices[3] == -1) {
 			// Render triangle
 			// Render triangle
-			renderTriangleFromPolygon(commandQueue, targetImage, depthBuffer, camera, polygon, 0, projected, filter, diffuse, light);
+			renderTriangleFromPolygon(commandQueue, targetImage, depthBuffer, camera, polygon, 0, projected, filter, this->diffuseMap, this->lightMap);
 		} else {
 		} else {
 			// Render quad
 			// Render quad
-			renderTriangleFromPolygon(commandQueue, targetImage, depthBuffer, camera, polygon, 0, projected, filter, diffuse, light);
-			renderTriangleFromPolygon(commandQueue, targetImage, depthBuffer, camera, polygon, 1, projected, filter, diffuse, light);
+			renderTriangleFromPolygon(commandQueue, targetImage, depthBuffer, camera, polygon, 0, projected, filter, this->diffuseMap, this->lightMap);
+			renderTriangleFromPolygon(commandQueue, targetImage, depthBuffer, camera, polygon, 1, projected, filter, this->diffuseMap, this->lightMap);
 		}
 		}
 	}
 	}
 }
 }
 
 
-void Part::renderDepth(ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, const ProjectedPoint* projected) const {
+void Part::renderDepth(const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, const ProjectedPoint* projected) const {
 	for (int p = 0; p < this->polygonBuffer.length(); p++) {
 	for (int p = 0; p < this->polygonBuffer.length(); p++) {
 		Polygon polygon = this->polygonBuffer[p];
 		Polygon polygon = this->polygonBuffer[p];
 		if (polygon.pointIndices[3] == -1) {
 		if (polygon.pointIndices[3] == -1) {
@@ -185,7 +182,7 @@ void Part::renderDepth(ImageF32* depthBuffer, const Transform3D &modelToWorldTra
 	}
 	}
 }
 }
 
 
-void ModelImpl::render(CommandQueue *commandQueue, ImageRgbaU8* targetImage, ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const {
+void ModelImpl::render(CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const {
 	if (camera.isBoxSeen(this->minBound, this->maxBound, modelToWorldTransform)) {
 	if (camera.isBoxSeen(this->minBound, this->maxBound, modelToWorldTransform)) {
 		// Transform and project all vertices
 		// Transform and project all vertices
 		int positionCount = positionBuffer.length();
 		int positionCount = positionBuffer.length();
@@ -199,7 +196,7 @@ void ModelImpl::render(CommandQueue *commandQueue, ImageRgbaU8* targetImage, Ima
 	}
 	}
 }
 }
 
 
-void ModelImpl::renderDepth(ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const {
+void ModelImpl::renderDepth(const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const {
 	if (camera.isBoxSeen(this->minBound, this->maxBound, modelToWorldTransform)) {
 	if (camera.isBoxSeen(this->minBound, this->maxBound, modelToWorldTransform)) {
 		// Transform and project all vertices
 		// Transform and project all vertices
 		int positionCount = positionBuffer.length();
 		int positionCount = positionBuffer.length();

+ 4 - 4
Source/DFPSR/implementation/render/model/Model.h

@@ -70,8 +70,8 @@ struct Part {
 	explicit Part(const ReadableString &name);
 	explicit Part(const ReadableString &name);
 	Part(const TextureRgbaU8 &diffuseMap, const TextureRgbaU8 &lightMap, const List<Polygon> &polygonBuffer, const String &name);
 	Part(const TextureRgbaU8 &diffuseMap, const TextureRgbaU8 &lightMap, const List<Polygon> &polygonBuffer, const String &name);
 	Part clone() const;
 	Part clone() const;
-	void render(CommandQueue *commandQueue, ImageRgbaU8* targetImage, ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, Filter filter, const ProjectedPoint* projected) const;
-	void renderDepth(ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, const ProjectedPoint* projected) const;
+	void render(CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, Filter filter, const ProjectedPoint* projected) const;
+	void renderDepth(const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera, const ProjectedPoint* projected) const;
 	int getPolygonCount() const;
 	int getPolygonCount() const;
 	int getPolygonVertexCount(int polygonIndex) const;
 	int getPolygonVertexCount(int polygonIndex) const;
 };
 };
@@ -128,8 +128,8 @@ public:
 	FVector4D getTexCoord(int partIndex, int polygonIndex, int vertexIndex) const;
 	FVector4D getTexCoord(int partIndex, int polygonIndex, int vertexIndex) const;
 	void setTexCoord(int partIndex, int polygonIndex, int vertexIndex, const FVector4D& texCoord);
 	void setTexCoord(int partIndex, int polygonIndex, int vertexIndex, const FVector4D& texCoord);
 	// Rendering
 	// Rendering
-	void render(CommandQueue *commandQueue, ImageRgbaU8* targetImage, ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const;
-	void renderDepth(ImageF32* depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const;
+	void render(CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const;
+	void renderDepth(const ImageF32 &depthBuffer, const Transform3D &modelToWorldTransform, const Camera &camera) const;
 };
 };
 
 
 }
 }

+ 36 - 18
Source/DFPSR/implementation/render/renderCore.cpp

@@ -281,26 +281,26 @@ static void renderTriangleWithShader(CommandQueue *commandQueue, const TriangleD
 
 
 // TODO: Move shader selection to Shader_RgbaMultiply and let models default to its shader factory function pointer as shader selection
 // TODO: Move shader selection to Shader_RgbaMultiply and let models default to its shader factory function pointer as shader selection
 void dsr::renderTriangleFromData(
 void dsr::renderTriangleFromData(
-  CommandQueue *commandQueue, ImageRgbaU8 *targetImage, ImageF32 *depthBuffer,
+  CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer,
   const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
   const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
-  Filter filter, const TextureRgbaU8 *diffuse, const TextureRgbaU8 *light,
+  Filter filter, const TextureRgbaU8 &diffuse, const TextureRgbaU8 &light,
   const TriangleTexCoords &texCoords, const TriangleColors &colors) {
   const TriangleTexCoords &texCoords, const TriangleColors &colors) {
 	// Get dimensions from both buffers
 	// Get dimensions from both buffers
-	int colorWidth = targetImage != nullptr ? image_getWidth(*targetImage) : 0;
-	int colorHeight = targetImage != nullptr ? image_getHeight(*targetImage) : 0;
-	int depthWidth = depthBuffer != nullptr ? image_getWidth(*depthBuffer) : 0;
-	int depthHeight = depthBuffer != nullptr ? image_getHeight(*depthBuffer) : 0;
+	int colorWidth = image_getWidth(targetImage);
+	int colorHeight = image_getHeight(targetImage);
+	int depthWidth = image_getWidth(depthBuffer);
+	int depthHeight = image_getHeight(depthBuffer);
 	// Combine dimensions
 	// Combine dimensions
 	int targetWidth, targetHeight;
 	int targetWidth, targetHeight;
-	if (targetImage != nullptr) {
+	if (image_exists(targetImage)) {
 		targetWidth = colorWidth;
 		targetWidth = colorWidth;
 		targetHeight = colorHeight;
 		targetHeight = colorHeight;
-		if (depthBuffer != nullptr) {
+		if (image_exists(depthBuffer)) {
 			assert(targetWidth == depthWidth);
 			assert(targetWidth == depthWidth);
 			assert(targetHeight == depthHeight);
 			assert(targetHeight == depthHeight);
 		}
 		}
 	} else {
 	} else {
-		if (depthBuffer != nullptr) {
+		if (image_exists(depthBuffer)) {
 			targetWidth = depthWidth;
 			targetWidth = depthWidth;
 			targetHeight = depthHeight;
 			targetHeight = depthHeight;
 		} else {
 		} else {
@@ -316,13 +316,31 @@ void dsr::renderTriangleFromData(
 	if (visibility != Visibility::Hidden) {
 	if (visibility != Visibility::Hidden) {
 		// Select an instance of the default shader
 		// Select an instance of the default shader
 		if (!(filter == Filter::Alpha && almostZero(colors.alpha))) {
 		if (!(filter == Filter::Alpha && almostZero(colors.alpha))) {
-			renderTriangleWithShader(commandQueue, TriangleDrawData(targetImage, depthBuffer, camera.perspective, filter, TriangleInput(diffuse, light, texCoords, colors), &processTriangle_RgbaMultiply), camera, triangle, clipBound);
+			renderTriangleWithShader (
+				commandQueue,
+				TriangleDrawData (
+					targetImage,
+					depthBuffer,
+					camera.perspective,
+					filter,
+					TriangleInput (
+						diffuse,
+						light,
+						texCoords,
+						colors
+					),
+					&processTriangle_RgbaMultiply
+				),
+				camera,
+				triangle,
+				clipBound
+			);
 		}
 		}
 	}
 	}
 }
 }
 
 
 template<bool AFFINE>
 template<bool AFFINE>
-static void executeTriangleDrawingDepth(ImageF32 *depthBuffer, const ITriangle2D& triangle, const IRect &clipBound) {
+static void executeTriangleDrawingDepth(const ImageF32 &depthBuffer, const ITriangle2D& triangle, const IRect &clipBound) {
 	int32_t rowCount = triangle.getBufferSize(clipBound, 1, 1);
 	int32_t rowCount = triangle.getBufferSize(clipBound, 1, 1);
 	if (rowCount > 0) {
 	if (rowCount > 0) {
 		int startRow;
 		int startRow;
@@ -331,8 +349,8 @@ static void executeTriangleDrawingDepth(ImageF32 *depthBuffer, const ITriangle2D
 		Projection projection = triangle.getProjection(FVector3D(), FVector3D(), !AFFINE); // TODO: Create a weight using only depth to save time
 		Projection projection = triangle.getProjection(FVector3D(), FVector3D(), !AFFINE); // TODO: Create a weight using only depth to save time
 		RowShape shape = RowShape(startRow, rowCount, rows.getUnsafe());
 		RowShape shape = RowShape(startRow, rowCount, rows.getUnsafe());
 		// Draw the triangle
 		// Draw the triangle
-		const int depthBufferStride = image_getStride(*depthBuffer);
-		SafePointer<float> depthDataRow = image_getSafePointer<float>(*depthBuffer, shape.startRow);
+		const int depthBufferStride = image_getStride(depthBuffer);
+		SafePointer<float> depthDataRow = image_getSafePointer<float>(depthBuffer, shape.startRow);
 		for (int32_t y = shape.startRow; y < shape.startRow + shape.rowCount; y++) {
 		for (int32_t y = shape.startRow; y < shape.startRow + shape.rowCount; y++) {
 			RowInterval row = shape.rows[y - shape.startRow];
 			RowInterval row = shape.rows[y - shape.startRow];
 			SafePointer<float> depthData = depthDataRow + row.left;
 			SafePointer<float> depthData = depthDataRow + row.left;
@@ -367,7 +385,7 @@ static void executeTriangleDrawingDepth(ImageF32 *depthBuffer, const ITriangle2D
 	}
 	}
 }
 }
 
 
-static void drawTriangleDepth(ImageF32 *depthBuffer, const Camera &camera, const IRect &clipBound, const ITriangle2D& triangle) {
+static void drawTriangleDepth(const ImageF32 &depthBuffer, const Camera &camera, const IRect &clipBound, const ITriangle2D& triangle) {
 	// Rounding sub-triangles to integer locations may reverse the direction of zero area triangles
 	// Rounding sub-triangles to integer locations may reverse the direction of zero area triangles
 	if (triangle.isFrontfacing()) {
 	if (triangle.isFrontfacing()) {
 		if (camera.perspective) {
 		if (camera.perspective) {
@@ -378,18 +396,18 @@ static void drawTriangleDepth(ImageF32 *depthBuffer, const Camera &camera, const
 	}
 	}
 }
 }
 
 
-static void drawSubTriangleDepth(ImageF32 *depthBuffer, const Camera &camera, const IRect &clipBound, const SubVertex &vertexA, const SubVertex &vertexB, const SubVertex &vertexC) {
+static void drawSubTriangleDepth(const ImageF32 &depthBuffer, const Camera &camera, const IRect &clipBound, const SubVertex &vertexA, const SubVertex &vertexB, const SubVertex &vertexC) {
 	ProjectedPoint posA = camera.cameraToScreen(vertexA.cs);
 	ProjectedPoint posA = camera.cameraToScreen(vertexA.cs);
 	ProjectedPoint posB = camera.cameraToScreen(vertexB.cs);
 	ProjectedPoint posB = camera.cameraToScreen(vertexB.cs);
 	ProjectedPoint posC = camera.cameraToScreen(vertexC.cs);
 	ProjectedPoint posC = camera.cameraToScreen(vertexC.cs);
 	drawTriangleDepth(depthBuffer, camera, clipBound, ITriangle2D(posA, posB, posC));
 	drawTriangleDepth(depthBuffer, camera, clipBound, ITriangle2D(posA, posB, posC));
 }
 }
 
 
-void dsr::renderTriangleFromDataDepth(ImageF32 *depthBuffer, const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC) {
+void dsr::renderTriangleFromDataDepth(const ImageF32 &depthBuffer, const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC) {
 	// Skip rendering if there's no target buffer
 	// Skip rendering if there's no target buffer
-	if (depthBuffer == nullptr) { return; }
+	if (!image_exists(depthBuffer)) { return; }
 	// Select a bound
 	// Select a bound
-	IRect clipBound = IRect::FromSize(image_getWidth(*depthBuffer), image_getHeight(*depthBuffer));
+	IRect clipBound = IRect::FromSize(image_getWidth(depthBuffer), image_getHeight(depthBuffer));
 	// Create a triangle
 	// Create a triangle
 	ITriangle2D triangle(posA, posB, posC);
 	ITriangle2D triangle(posA, posB, posC);
 	// Only draw visible triangles
 	// Only draw visible triangles

+ 6 - 6
Source/DFPSR/implementation/render/renderCore.h

@@ -34,9 +34,9 @@ namespace dsr {
 
 
 struct TriangleDrawData {
 struct TriangleDrawData {
 	// Color target
 	// Color target
-	ImageRgbaU8 *targetImage;
+	ImageRgbaU8 targetImage;
 	// Depth target
 	// Depth target
-	ImageF32 *depthBuffer;
+	ImageF32 depthBuffer;
 	// When perspective is used, the depth buffer stores 1 / depth instead of linear depth.
 	// When perspective is used, the depth buffer stores 1 / depth instead of linear depth.
 	bool perspective;
 	bool perspective;
 	// The target blending method
 	// The target blending method
@@ -45,7 +45,7 @@ struct TriangleDrawData {
 	TriangleInput triangleInput;
 	TriangleInput triangleInput;
 	// Function pointer to the method that will process the command
 	// Function pointer to the method that will process the command
 	DRAW_CALLBACK_TYPE processTriangle;
 	DRAW_CALLBACK_TYPE processTriangle;
-	TriangleDrawData(ImageRgbaU8 *targetImage, ImageF32 *depthBuffer, bool perspective, Filter filter, const TriangleInput &triangleInput, DRAW_CALLBACK_TYPE processTriangle)
+	TriangleDrawData(const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer, bool perspective, Filter filter, const TriangleInput &triangleInput, DRAW_CALLBACK_TYPE processTriangle)
 	: targetImage(targetImage), depthBuffer(depthBuffer), perspective(perspective), filter(filter), triangleInput(triangleInput), processTriangle(processTriangle) {}
 	: targetImage(targetImage), depthBuffer(depthBuffer), perspective(perspective), filter(filter), triangleInput(triangleInput), processTriangle(processTriangle) {}
 };
 };
 
 
@@ -102,12 +102,12 @@ public:
 // targetImage can be null to avoid using the pixel shader.
 // targetImage can be null to avoid using the pixel shader.
 // depthBuffer can be null to render without depth buffering.
 // depthBuffer can be null to render without depth buffering.
 void renderTriangleFromData(
 void renderTriangleFromData(
-  CommandQueue *commandQueue, ImageRgbaU8 *targetImage, ImageF32 *depthBuffer,
+  CommandQueue *commandQueue, const ImageRgbaU8 &targetImage, const ImageF32 &depthBuffer,
   const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
   const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC,
-  Filter filter, const TextureRgbaU8 *diffuse, const TextureRgbaU8 *light,
+  Filter filter, const TextureRgbaU8 &diffuse, const TextureRgbaU8 &light,
   const TriangleTexCoords &texCoords, const TriangleColors &colors
   const TriangleTexCoords &texCoords, const TriangleColors &colors
 );
 );
-void renderTriangleFromDataDepth(ImageF32 *depthBuffer, const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC);
+void renderTriangleFromDataDepth(const ImageF32 &depthBuffer, const Camera &camera, const ProjectedPoint &posA, const ProjectedPoint &posB, const ProjectedPoint &posC);
 
 
 }
 }
 
 

+ 9 - 9
Source/DFPSR/implementation/render/shader/RgbaMultiply.h

@@ -35,18 +35,18 @@
 namespace dsr {
 namespace dsr {
 
 
 struct RgbaMultiply_data {
 struct RgbaMultiply_data {
-	const TextureRgbaU8 *diffuseMap; // Mip-mapping is allowed for diffuse textures.
-	const TextureRgbaU8 *lightMap; // Mip-mapping is not allowed for lightmaps, because it would increase the number of shaders to compile and still look worse.
+	const TextureRgbaU8 diffuseMap; // Mip-mapping is allowed for diffuse textures.
+	const TextureRgbaU8 lightMap; // Mip-mapping is not allowed for lightmaps, because it would increase the number of shaders to compile and still look worse.
 	// Planar format with each vector representing the three triangle corners
 	// Planar format with each vector representing the three triangle corners
 	const TriangleTexCoords texCoords;
 	const TriangleTexCoords texCoords;
 	const TriangleColors colors;
 	const TriangleColors colors;
 	// Normalize the color product by pre-multiplying the vertex colors
 	// Normalize the color product by pre-multiplying the vertex colors
 	float getVertexScale() {
 	float getVertexScale() {
 		float result = 255.0f; // Scale from normalized to byte for the output
 		float result = 255.0f; // Scale from normalized to byte for the output
-		if (texture_exists(*(this->diffuseMap))) {
+		if (texture_exists(this->diffuseMap)) {
 			result *= 1.0f / 255.0f; // Normalize the diffuse map from 0..255 to 0..1 by dividing the vertex color
 			result *= 1.0f / 255.0f; // Normalize the diffuse map from 0..255 to 0..1 by dividing the vertex color
 		}
 		}
-		if (texture_exists(*(this->lightMap))) {
+		if (texture_exists(this->lightMap)) {
 			result *= 1.0f / 255.0f; // Normalize the light map from 0..255 to 0..1 by dividing the vertex color
 			result *= 1.0f / 255.0f; // Normalize the light map from 0..255 to 0..1 by dividing the vertex color
 		}
 		}
 		return result;
 		return result;
@@ -107,16 +107,16 @@ inline Rgba_F32<U32x4, F32x4> getPixels_2x2(void *data, const F32x4x3 &vertexWei
 
 
 // The process method to take a function pointer to.
 // The process method to take a function pointer to.
 //    Must have the same signature as drawCallbackTemplate in Shader.h.
 //    Must have the same signature as drawCallbackTemplate in Shader.h.
-static void processTriangle_RgbaMultiply(const TriangleInput &triangleInput, ImageRgbaU8 *colorBuffer, ImageF32 *depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) {
+static void processTriangle_RgbaMultiply(const TriangleInput &triangleInput, const ImageRgbaU8 &colorBuffer, const ImageF32 &depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) {
 	// The pointers to textures may not be null, but can point to empty textures.
 	// The pointers to textures may not be null, but can point to empty textures.
 	RgbaMultiply_data data = RgbaMultiply_data(triangleInput);
 	RgbaMultiply_data data = RgbaMultiply_data(triangleInput);
 	bool hasVertexFade = !(almostSame(data.colors.red) && almostSame(data.colors.green) && almostSame(data.colors.blue) && almostSame(data.colors.alpha));
 	bool hasVertexFade = !(almostSame(data.colors.red) && almostSame(data.colors.green) && almostSame(data.colors.blue) && almostSame(data.colors.alpha));
 	bool colorless = almostOne(data.colors.red) && almostOne(data.colors.green) && almostOne(data.colors.blue) && almostOne(data.colors.alpha);
 	bool colorless = almostOne(data.colors.red) && almostOne(data.colors.green) && almostOne(data.colors.blue) && almostOne(data.colors.alpha);
 	// TODO: Should non-existing textures use null pointers in the data, or pointers to empty textures?
 	// TODO: Should non-existing textures use null pointers in the data, or pointers to empty textures?
-	if (texture_exists(*(data.diffuseMap))) {
-		bool hasDiffusePyramid = texture_hasPyramid(*(data.diffuseMap));
+	if (texture_exists(data.diffuseMap)) {
+		bool hasDiffusePyramid = texture_hasPyramid(data.diffuseMap);
 		// TODO: Avoid generating mip levels for the lightmap texture instead of hard-coding it to no mip levels.
 		// TODO: Avoid generating mip levels for the lightmap texture instead of hard-coding it to no mip levels.
-		if (texture_exists(*(data.lightMap))) {
+		if (texture_exists(data.lightMap)) {
 			if (hasVertexFade) { // DiffuseLightVertex
 			if (hasVertexFade) { // DiffuseLightVertex
 				if (hasDiffusePyramid) { // With mipmap
 				if (hasDiffusePyramid) { // With mipmap
 					fillShape(&data, getPixels_2x2<true, false, true, true, false>, colorBuffer, depthBuffer, triangle, projection, shape, filter);
 					fillShape(&data, getPixels_2x2<true, false, true, true, false>, colorBuffer, depthBuffer, triangle, projection, shape, filter);
@@ -154,7 +154,7 @@ static void processTriangle_RgbaMultiply(const TriangleInput &triangleInput, Ima
 			}
 			}
 		}
 		}
 	} else {
 	} else {
-		if (texture_exists(*(data.lightMap))) {
+		if (texture_exists(data.lightMap)) {
 			if (hasVertexFade) { // LightVertex
 			if (hasVertexFade) { // LightVertex
 				fillShape(&data, getPixels_2x2<false, false, true, true, false>, colorBuffer, depthBuffer, triangle, projection, shape, filter);
 				fillShape(&data, getPixels_2x2<false, false, true, true, false>, colorBuffer, depthBuffer, triangle, projection, shape, filter);
 			} else {
 			} else {

+ 4 - 4
Source/DFPSR/implementation/render/shader/Shader.h

@@ -56,16 +56,16 @@ struct TriangleColors {
 };
 };
 
 
 struct TriangleInput {
 struct TriangleInput {
-	const TextureRgbaU8 *diffuseMap;
-	const TextureRgbaU8 *lightMap;
+	const TextureRgbaU8 diffuseMap;
+	const TextureRgbaU8 lightMap;
 	const TriangleTexCoords texCoords;
 	const TriangleTexCoords texCoords;
 	const TriangleColors colors;
 	const TriangleColors colors;
-	TriangleInput(const TextureRgbaU8 *diffuseMap, const TextureRgbaU8 *lightMap, const TriangleTexCoords &texCoords, const TriangleColors &colors)
+	TriangleInput(const TextureRgbaU8 &diffuseMap, const TextureRgbaU8 &lightMap, const TriangleTexCoords &texCoords, const TriangleColors &colors)
 	: diffuseMap(diffuseMap), lightMap(lightMap), texCoords(texCoords), colors(colors) {}
 	: diffuseMap(diffuseMap), lightMap(lightMap), texCoords(texCoords), colors(colors) {}
 };
 };
 
 
 // The template for function pointers doing the work
 // The template for function pointers doing the work
-inline void drawCallbackTemplate(const TriangleInput &triangleInput, ImageRgbaU8 *colorBuffer, ImageF32 *depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) {}
+inline void drawCallbackTemplate(const TriangleInput &triangleInput, const ImageRgbaU8 &colorBuffer, const ImageF32 &depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) {}
 using DRAW_CALLBACK_TYPE = decltype(&drawCallbackTemplate);
 using DRAW_CALLBACK_TYPE = decltype(&drawCallbackTemplate);
 
 
 }
 }

+ 19 - 19
Source/DFPSR/implementation/render/shader/fillerTemplates.h

@@ -244,29 +244,29 @@ inline void fillRowSuper(void *data, PixelShadingCallback pixelShaderFunction, S
 }
 }
 
 
 template<bool COLOR_WRITE, bool DEPTH_READ, bool DEPTH_WRITE, Filter FILTER, bool AFFINE>
 template<bool COLOR_WRITE, bool DEPTH_READ, bool DEPTH_WRITE, Filter FILTER, bool AFFINE>
-inline void fillShapeSuper(void *data, PixelShadingCallback pixelShaderFunction, ImageRgbaU8 *colorBuffer, ImageF32 *depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape) {
+inline void fillShapeSuper(void *data, PixelShadingCallback pixelShaderFunction, const ImageRgbaU8 &colorBuffer, const ImageF32 &depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape) {
 	// Prepare constants
 	// Prepare constants
-	const int targetStride = colorBuffer ? image_getStride(*colorBuffer) : 0;
-	const int depthBufferStride = depthBuffer ? image_getStride(*depthBuffer) : 0;
+	const int targetStride = image_getStride(colorBuffer);
+	const int depthBufferStride = image_getStride(depthBuffer);
 	const FVector3D doublePWeightDx = projection.pWeightDx * 2.0f;
 	const FVector3D doublePWeightDx = projection.pWeightDx * 2.0f;
-	const int colorRowSize = colorBuffer ? image_getWidth(*colorBuffer) * sizeof(uint32_t) : 0;
-	const int depthRowSize = depthBuffer ? image_getWidth(*depthBuffer) * sizeof(float) : 0;
-	const PackOrder& targetPackingOrder = colorBuffer ? image_getPackOrder(*colorBuffer) : PackOrder::getPackOrder(PackOrderIndex::RGBA);
-	const int colorHeight = colorBuffer ? image_getHeight(*colorBuffer) : 0;
-	const int depthHeight = depthBuffer ? image_getHeight(*depthBuffer) : 0;
+	const int colorRowSize = image_getWidth(colorBuffer) * sizeof(uint32_t);
+	const int depthRowSize = image_getWidth(depthBuffer) * sizeof(float);
+	const PackOrder& targetPackingOrder = image_exists(colorBuffer) ? image_getPackOrder(colorBuffer) : PackOrder::getPackOrder(PackOrderIndex::RGBA);
+	const int colorHeight = image_getHeight(colorBuffer);
+	const int depthHeight = image_getHeight(depthBuffer);
 	const int maxHeight = colorHeight > depthHeight ? colorHeight : depthHeight;
 	const int maxHeight = colorHeight > depthHeight ? colorHeight : depthHeight;
 
 
 	// Initialize row pointers for color buffer
 	// Initialize row pointers for color buffer
 	SafePointer<uint32_t> pixelDataUpper, pixelDataLower, pixelDataUpperRow, pixelDataLowerRow;
 	SafePointer<uint32_t> pixelDataUpper, pixelDataLower, pixelDataUpperRow, pixelDataLowerRow;
 	if (COLOR_WRITE) {
 	if (COLOR_WRITE) {
-		pixelDataUpperRow = image_getSafePointer<uint32_t>(*colorBuffer, shape.startRow);
+		pixelDataUpperRow = image_getSafePointer<uint32_t>(colorBuffer, shape.startRow);
 		pixelDataLowerRow = pixelDataUpperRow; pixelDataLowerRow.increaseBytes(targetStride);
 		pixelDataLowerRow = pixelDataUpperRow; pixelDataLowerRow.increaseBytes(targetStride);
 	}
 	}
 
 
 	// Initialize row pointers for depth buffer
 	// Initialize row pointers for depth buffer
 	SafePointer<float> depthDataUpper, depthDataLower, depthDataUpperRow, depthDataLowerRow;
 	SafePointer<float> depthDataUpper, depthDataLower, depthDataUpperRow, depthDataLowerRow;
 	if (DEPTH_READ || DEPTH_WRITE) {
 	if (DEPTH_READ || DEPTH_WRITE) {
-		depthDataUpperRow = image_getSafePointer<float>(*depthBuffer, shape.startRow);
+		depthDataUpperRow = image_getSafePointer<float>(depthBuffer, shape.startRow);
 		depthDataLowerRow = depthDataUpperRow; depthDataLowerRow.increaseBytes(depthBufferStride);
 		depthDataLowerRow = depthDataUpperRow; depthDataLowerRow.increaseBytes(depthBufferStride);
 	}
 	}
 
 
@@ -384,9 +384,9 @@ inline void fillShapeSuper(void *data, PixelShadingCallback pixelShaderFunction,
 	}
 	}
 }
 }
 
 
-inline void fillShape(void *data, PixelShadingCallback pixelShaderFunction, ImageRgbaU8 *colorBuffer, ImageF32 *depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) {
-	bool hasColorBuffer = colorBuffer != nullptr;
-	bool hasDepthBuffer = depthBuffer != nullptr;
+static inline void fillShape(void *data, PixelShadingCallback pixelShaderFunction, const ImageRgbaU8 &colorBuffer, const ImageF32 &depthBuffer, const ITriangle2D &triangle, const Projection &projection, const RowShape &shape, Filter filter) {
+	bool hasColorBuffer = image_exists(colorBuffer);
+	bool hasDepthBuffer = image_exists(depthBuffer);
 	if (projection.affine) {
 	if (projection.affine) {
 		if (hasDepthBuffer) {
 		if (hasDepthBuffer) {
 			if (hasColorBuffer) {
 			if (hasColorBuffer) {
@@ -399,16 +399,16 @@ inline void fillShape(void *data, PixelShadingCallback pixelShaderFunction, Imag
 				}
 				}
 			} else {
 			} else {
 				// Solid depth
 				// Solid depth
-				fillShapeSuper<false, true, true, Filter::Solid, true>(data, pixelShaderFunction, nullptr, depthBuffer, triangle, projection, shape);
+				fillShapeSuper<false, true, true, Filter::Solid, true>(data, pixelShaderFunction, ImageRgbaU8(), depthBuffer, triangle, projection, shape);
 			}
 			}
 		} else {
 		} else {
 			if (hasColorBuffer) {
 			if (hasColorBuffer) {
 				if (filter != Filter::Solid) {
 				if (filter != Filter::Solid) {
 					// Alpha filtering without depth buffer
 					// Alpha filtering without depth buffer
-					fillShapeSuper<true, false, false, Filter::Alpha, true>(data, pixelShaderFunction, colorBuffer, nullptr, triangle, projection, shape);
+					fillShapeSuper<true, false, false, Filter::Alpha, true>(data, pixelShaderFunction, colorBuffer, ImageF32(), triangle, projection, shape);
 				} else {
 				} else {
 					// Solid without depth buffer
 					// Solid without depth buffer
-					fillShapeSuper<true, false, false, Filter::Solid, true>(data, pixelShaderFunction, colorBuffer, nullptr, triangle, projection, shape);
+					fillShapeSuper<true, false, false, Filter::Solid, true>(data, pixelShaderFunction, colorBuffer, ImageF32(), triangle, projection, shape);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -424,16 +424,16 @@ inline void fillShape(void *data, PixelShadingCallback pixelShaderFunction, Imag
 				}
 				}
 			} else {
 			} else {
 				// Solid depth
 				// Solid depth
-				fillShapeSuper<false, true, true, Filter::Solid, false>(data, pixelShaderFunction, nullptr, depthBuffer, triangle, projection, shape);
+				fillShapeSuper<false, true, true, Filter::Solid, false>(data, pixelShaderFunction, ImageRgbaU8(), depthBuffer, triangle, projection, shape);
 			}
 			}
 		} else {
 		} else {
 			if (hasColorBuffer) {
 			if (hasColorBuffer) {
 				if (filter != Filter::Solid) {
 				if (filter != Filter::Solid) {
 					// Alpha filtering without depth buffer
 					// Alpha filtering without depth buffer
-					fillShapeSuper<true, false, false, Filter::Alpha, false>(data, pixelShaderFunction, colorBuffer, nullptr, triangle, projection, shape);
+					fillShapeSuper<true, false, false, Filter::Alpha, false>(data, pixelShaderFunction, colorBuffer, ImageF32(), triangle, projection, shape);
 				} else {
 				} else {
 					// Solid without depth buffer
 					// Solid without depth buffer
-					fillShapeSuper<true, false, false, Filter::Solid, false>(data, pixelShaderFunction, colorBuffer, nullptr, triangle, projection, shape);
+					fillShapeSuper<true, false, false, Filter::Solid, false>(data, pixelShaderFunction, colorBuffer, ImageF32(), triangle, projection, shape);
 				}
 				}
 			}
 			}
 		}
 		}

+ 8 - 8
Source/DFPSR/implementation/render/shader/shaderMethods.h

@@ -64,21 +64,21 @@ namespace shaderMethods {
 	  bool MIP_INSIDE = false,
 	  bool MIP_INSIDE = false,
 	  bool HIGHEST_RESOLUTION = false
 	  bool HIGHEST_RESOLUTION = false
 	>
 	>
-	inline U32x4 sample_U32(const TextureRgbaU8 *source, const F32x4 &u, const F32x4 &v) {
+	inline U32x4 sample_U32(const TextureRgbaU8 &source, const F32x4 &u, const F32x4 &v) {
 		if (INTERPOLATION == Interpolation::NN) {
 		if (INTERPOLATION == Interpolation::NN) {
 			if (HIGHEST_RESOLUTION) {
 			if (HIGHEST_RESOLUTION) {
-				return texture_sample_nearest<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(*source, u, v, 0u);
+				return texture_sample_nearest<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(source, u, v, 0u);
 			} else {
 			} else {
 				// TODO: Calculate MIP levels using a separate rendering stage with sparse resolution writing results into thread-local memory.
 				// TODO: Calculate MIP levels using a separate rendering stage with sparse resolution writing results into thread-local memory.
-				uint32_t mipLevel = texture_getMipLevelIndex<F32x4>(*source, u, v);
-				return texture_sample_nearest<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(*source, u, v, mipLevel);
+				uint32_t mipLevel = texture_getMipLevelIndex<F32x4>(source, u, v);
+				return texture_sample_nearest<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(source, u, v, mipLevel);
 			}
 			}
 		} else {
 		} else {
 			if (HIGHEST_RESOLUTION) {
 			if (HIGHEST_RESOLUTION) {
-				return texture_sample_bilinear<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(*source, u, v, 0u);
+				return texture_sample_bilinear<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(source, u, v, 0u);
 			} else {
 			} else {
-				uint32_t mipLevel = texture_getMipLevelIndex<F32x4>(*source, u, v);
-				return texture_sample_bilinear<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(*source, u, v, mipLevel);
+				uint32_t mipLevel = texture_getMipLevelIndex<F32x4>(source, u, v);
+				return texture_sample_bilinear<SQUARE, SINGLE_LAYER, MIP_INSIDE, HIGHEST_RESOLUTION>(source, u, v, mipLevel);
 			}
 			}
 		}
 		}
 	}
 	}
@@ -90,7 +90,7 @@ namespace shaderMethods {
 	  bool MIP_INSIDE = false,
 	  bool MIP_INSIDE = false,
 	  bool HIGHEST_RESOLUTION = false
 	  bool HIGHEST_RESOLUTION = false
 	>
 	>
-	inline Rgba_F32<U32x4, F32x4> sample_F32(const TextureRgbaU8 *source, const F32x4 &u, const F32x4 &v) {
+	inline Rgba_F32<U32x4, F32x4> sample_F32(const TextureRgbaU8 &source, const F32x4 &u, const F32x4 &v) {
 		return Rgba_F32<U32x4, F32x4>(sample_U32<INTERPOLATION, SQUARE, SINGLE_LAYER, XY_INSIDE, MIP_INSIDE, HIGHEST_RESOLUTION>(source, u, v));
 		return Rgba_F32<U32x4, F32x4>(sample_U32<INTERPOLATION, SQUARE, SINGLE_LAYER, XY_INSIDE, MIP_INSIDE, HIGHEST_RESOLUTION>(source, u, v));
 	}
 	}
 }
 }