a rewrite (maybe rust) is coming SOON

This commit is contained in:
Benjamin Kyd
2023-03-15 23:39:24 +00:00
parent 617750ea47
commit 60a8b83eee
4 changed files with 254 additions and 308 deletions

View File

@@ -0,0 +1,5 @@
#include "kdtree.hpp"
#include <algorithm>
#include <limits>

View File

@@ -1,203 +1,135 @@
#include <hart_graphics.hpp> #pragma once
#include <tracing/ray.hpp>
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include <limits> #include <limits>
#include <utility>
#include <array> #include <hart_graphics.hpp>
#include <iostream>
#include <tracing/ray.hpp>
#include <yolo/yolo.hpp>
using namespace inferno; using namespace inferno;
inline bool AABBIntersection(glm::vec3 min, glm::vec3 max, const Ray* r) struct Triangle
{ {
float tmin = 0.0, tmax = INFINITY; unsigned int indices[3];
glm::vec3 invDir = 1.0f / r->Direction; };
for (int i = 0; i < 3; ++i) { struct AABB
float t1 = (min[i] - r->Origin[i]) * invDir[i]; {
float t2 = (max[i] - r->Origin[i]) * invDir[i]; glm::vec3 min;
glm::vec3 max;
};
tmin = std::max(tmin, std::min(t1, t2)); struct KDNode
tmax = std::min(tmax, std::max(t1, t2)); {
AABB aabb;
unsigned int splitAxis;
std::vector<Triangle> triangles;
KDNode *left;
KDNode *right;
};
inline AABB createAABB(const std::vector<glm::vec3>& vertices, const std::vector<Triangle>& triangles)
{
glm::vec3 min(std::numeric_limits<float>::max());
glm::vec3 max(std::numeric_limits<float>::min());
for (const auto& triangle : triangles)
{
for (int i = 0; i < 3; ++i)
{
min = glm::min(min, vertices[triangle.indices[i]]);
max = glm::max(max, vertices[triangle.indices[i]]);
}
} }
bool hit = (tmin <= tmax); return { min, max };
//if (hit) {
//std::cout << "Ray hits AABB: " << tmin << ", " << tmax << std::endl;
//} else {
//std::cout << "Ray misses AABB" << std::endl;
//}
return hit;
} }
struct KDNode { inline bool intersectAABB(const AABB& aabb, const glm::vec3& origin, const glm::vec3& direction, float& tNear, float& tFar)
uint32_t TriIdx; {
glm::vec3 MinBounds; for (int i = 0; i < 3; ++i)
glm::vec3 MaxBounds; {
KDNode* LeftChild; float invDirection = 1.0f / direction[i];
KDNode* RightChild; float t0 = (aabb.min[i] - origin[i]) * invDirection;
float t1 = (aabb.max[i] - origin[i]) * invDirection;
KDNode(uint32_t triIdx, glm::vec3 minBounds, glm::vec3 maxBounds) if (invDirection < 0.0f)
: TriIdx(triIdx), MinBounds(minBounds), MaxBounds(maxBounds), LeftChild(nullptr), RightChild(nullptr) {}
~KDNode() {
delete LeftChild;
delete RightChild;
}
void setLeftChild(KDNode* child) {
LeftChild = child;
updateBounds();
}
void setRightChild(KDNode* child) {
RightChild = child;
updateBounds();
}
void updateBounds() {
if (LeftChild && RightChild) {
MinBounds = glm::min(LeftChild->MinBounds, RightChild->MinBounds);
MaxBounds = glm::max(LeftChild->MaxBounds, RightChild->MaxBounds);
}
}
};
class KDTree {
public:
KDTree(float* vertices, uint32_t* indices, std::vector<uint32_t>& indicesToProcess, uint32_t startIdx, uint32_t endIdx, uint32_t depthLimit)
: mVertices(vertices), mIndices(indices), mDepthLimit(depthLimit), mRoot(nullptr)
{ {
if (indicesToProcess.size() == 0) { std::swap(t0, t1);
return;
}
mRoot = buildNode(indicesToProcess, startIdx, endIdx, 0);
} }
~KDTree() { tNear = t0 > tNear ? t0 : tNear;
delete mRoot; tFar = t1 < tFar ? t1 : tFar;
if (tNear > tFar)
{
return false;
} }
}
void intersect(const Ray* ray, std::vector<uint32_t>& outIndices) { return true;
intersect(mRoot, ray, outIndices); }
}
KDNode* getRoot() const { inline KDNode* buildKDTree(const std::vector<glm::vec3>& vertices, std::vector<Triangle>& triangles, unsigned int depth = 0)
return mRoot; {
} if (triangles.empty())
{
return nullptr;
}
void printTree(KDNode* node, int depth) const { unsigned int splitAxis = depth % 3;
if (!node) { std::sort(triangles.begin(), triangles.end(), [&](const Triangle& a, const Triangle& b) {
return; return vertices[a.indices[0]][splitAxis] < vertices[b.indices[0]][splitAxis];
} });
for (int i = 0; i < depth; i++) { size_t midIdx = triangles.size() / 2;
std::cout << "-"; KDNode* node = new KDNode;
} node->splitAxis = splitAxis;
std::cout << " " << glm::to_string(node->MinBounds) << " " << glm::to_string(node->MaxBounds) << ": " << node->TriIdx << "\n"; node->aabb = createAABB(vertices, triangles);
node->triangles.push_back(triangles[midIdx]);
printTree(node->LeftChild, depth + 1); std::vector<Triangle> leftTriangles(triangles.begin(), triangles.begin() + midIdx);
printTree(node->RightChild, depth + 1); std::vector<Triangle> rightTriangles(triangles.begin() + midIdx + 1, triangles.end());
}
private: node->left = buildKDTree(vertices, leftTriangles, depth + 1);
KDNode* buildNode(std::vector<uint32_t>& indicesToProcess, uint32_t startIdx, uint32_t endIdx, uint32_t depth) { node->right = buildKDTree(vertices, rightTriangles, depth + 1);
if (startIdx >= endIdx || depth >= mDepthLimit) {
return nullptr;
}
if (endIdx - startIdx == 1) { return node;
return new KDNode(indicesToProcess[startIdx], getVertexBounds(mIndices[indicesToProcess[startIdx] * 3]), getVertexBounds(mIndices[indicesToProcess[startIdx] * 3])); }
}
glm::vec3 minBounds(INFINITY), maxBounds(-INFINITY); inline void intersectKDTree(KDNode* node, const glm::vec3& origin, const glm::vec3& direction, std::vector<Triangle>& hitCandidates) {
for (uint32_t i = startIdx; i < endIdx; ++i) { if (!node)
const glm::vec3& v0 = getVertexBounds(mIndices[indicesToProcess[i] * 3]); {
const glm::vec3& v1 = getVertexBounds(mIndices[indicesToProcess[i] * 3 + 1]); return;
const glm::vec3& v2 = getVertexBounds(mIndices[indicesToProcess[i] * 3 + 2]); }
minBounds = glm::min(minBounds, glm::min(v0, glm::min(v1, v2)));
maxBounds = glm::max(maxBounds, glm::max(v0, glm::max(v1, v2)));
}
uint32_t axis = depth % 3; float tNear = -std::numeric_limits<float>::max();
uint32_t median = partition(indicesToProcess, startIdx, endIdx, axis); float tFar = std::numeric_limits<float>::max();
bool isPartitionValid = checkPartition(indicesToProcess, startIdx, endIdx, axis, median); if (!intersectAABB(node->aabb, origin, direction, tNear, tFar))
if (!isPartitionValid) { {
std::cout << "Partition failed!" << std::endl; return;
} }
KDNode* node = new KDNode(0, minBounds, maxBounds); if (!node->left && !node->right)
{
hitCandidates.insert(hitCandidates.end(), node->triangles.begin(), node->triangles.end());
return;
}
std::vector<uint32_t> leftIndices(indicesToProcess.begin() + startIdx, indicesToProcess.begin() + median); intersectKDTree(node->left, origin, direction, hitCandidates);
std::vector<uint32_t> rightIndices(indicesToProcess.begin() + median, indicesToProcess.begin() + endIdx); intersectKDTree(node->right, origin, direction, hitCandidates);
}
node->setLeftChild(buildNode(leftIndices, startIdx, median, depth + 1)); inline void deleteKDTree(KDNode* node)
node->setRightChild(buildNode(rightIndices, 0, endIdx - median, depth + 1)); {
if (node)
{
deleteKDTree(node->left);
deleteKDTree(node->right);
delete node;
}
}
return node;
}
void intersect(const KDNode* node, const Ray* ray, std::vector<uint32_t>& outIndices) {
if (!node) {
return;
}
//std::cout << "Checking node bounds: " << glm::to_string(node->MinBounds) << " " << glm::to_string(node->MaxBounds) << std::endl;
if (AABBIntersection(node->MinBounds, node->MaxBounds, ray)) {
//std::cout << "Ray intersects node, num tris: " << (node->LeftChild || node->RightChild ? -1 : 1) << std::endl;
if (node->LeftChild || node->RightChild) {
intersect(node->LeftChild, ray, outIndices);
intersect(node->RightChild, ray, outIndices);
}
else {
//std::cout << "Ray hit leaf node with triangle index: " << node->TriIdx << std::endl;
outIndices.push_back(node->TriIdx);
}
}
else {
//std::cout << "Ray does not intersect node" << std::endl;
}
//std::cout << std::endl;
//exit(0);
}
glm::vec3 getVertexBounds(uint32_t index) const {
return { mVertices[index * 3], mVertices[index * 3 + 1], mVertices[index * 3 + 2] };
}
// TODO: this could definately be more advanced, at the moment is is a split down the middle
uint32_t partition(std::vector<uint32_t>& indicesToProcess, uint32_t startIdx, uint32_t endIdx, uint32_t axis) {
uint32_t medianIdx = (startIdx + endIdx) / 2;
glm::vec3 pivot = getVertexBounds(mIndices[indicesToProcess[medianIdx] * 3]);
std::nth_element(indicesToProcess.begin() + startIdx, indicesToProcess.begin() + medianIdx, indicesToProcess.begin() + endIdx,
[this, &pivot, axis](uint32_t a, uint32_t b) { return getVertexBounds(mIndices[a * 3])[axis] < getVertexBounds(mIndices[b * 3])[axis]; });
return medianIdx;
}
bool checkPartition(std::vector<uint32_t>& indicesToProcess, uint32_t startIdx, uint32_t endIdx, uint32_t axis, uint32_t median) {
for (uint32_t i = startIdx; i < median; ++i) {
if (getVertexBounds(mIndices[indicesToProcess[i] * 3])[axis] > getVertexBounds(mIndices[indicesToProcess[median] * 3])[axis]) {
return false;
}
}
for (uint32_t i = median + 1; i < endIdx; ++i) {
if (getVertexBounds(mIndices[indicesToProcess[i] * 3])[axis] < getVertexBounds(mIndices[indicesToProcess[median] * 3])[axis]) {
return false;
}
}
return true;
}
private:
float* mVertices;
uint32_t* mIndices;
uint32_t mDepthLimit;
KDNode* mRoot;
};

View File

@@ -16,158 +16,166 @@ using namespace inferno;
class HARTCPU : public HARTModule class HARTCPU : public HARTModule
{ {
public: public:
HARTCPU() HARTCPU()
{
mMasterWorker = std::thread(&HARTCPU::intersectMasterWorker, this);
mLogModule = yolo::registerModule("hartcpu", "\u001b[35;1m");
}
~HARTCPU()
{
this->stop(true);
mMasterWorker.detach();
}
void submitTris(void* vert,
void* norm,
int vc,
void* indices,
int ic) override
{
std::lock_guard<std::mutex> lock(_mData);
mState = EModuleState::Build;
mVert = (float*)vert; mNorm = (float*)norm; mVc = vc; mIndices = (uint32_t*)indices; mIc = ic;
yolo::info(mLogModule, "Recieved {} verticies ({}) and {} indicies ({})", vc / 3, vert, ic / 3, indices);
std::vector<uint32_t> indicesToProcess(ic / 3);
for (uint32_t i = 0; i < ic / 3; ++i)
{ {
indicesToProcess[i] = i; mMasterWorker = std::thread(&HARTCPU::intersectMasterWorker, this);
mLogModule = yolo::registerModule("hartcpu", "\u001b[35;1m");
rootNode = nullptr;
} }
mKdTree = new KDTree(mVert, mIndices, indicesToProcess, 0, indicesToProcess.size() - 1, 10); ~HARTCPU()
mKdTree->printTree(mKdTree->getRoot(), 1);
yolo::info(mLogModule, "Accelerator ready..");
mState = EModuleState::Idle;
}
void updateTris() override {}
void start() override
{
std::lock_guard<std::mutex> signalLock(_mSignalMut);
mIsRunning = true;
mState = EModuleState::Trace;
_mSignalCv.notify_all();
yolo::info(mLogModule, "Signal master to start");
{ {
std::unique_lock<std::mutex> doneLock(_mDoneMut); this->stop(true);
_mDoneCv.wait(doneLock, [this] { return mState == EModuleState::Idle; }); mMasterWorker.detach();
if (rootNode) {
deleteKDTree(rootNode);
}
} }
}
void stop(bool interrupt) override void submitTris(void* vert,
{ void* norm,
if (!interrupt) int vc,
void* indices,
int ic) override
{ {
mIsRunning = false; std::lock_guard<std::mutex> lock(_mData);
return;
}
// TODO: Find a way to force the thread to hault
}
void intersectMasterWorker() mState = EModuleState::Build;
{ mVert = (float*)vert; mNorm = (float*)norm; mVc = vc; mIndices = (uint32_t*)indices; mIc = ic;
for (;;) yolo::info(mLogModule, "Recieved {} verticies ({}) and {} indicies ({})", vc / 3, vert, ic / 3, indices);
{
std::unique_lock<std::mutex> lock(_mData); std::vector<glm::vec3> vertices(mVc / 3);
if (!mIsRunning) for (size_t i = 0; i < vertices.size(); ++i) {
{ vertices[i] = glm::vec3(mVert[i * 3], mVert[i * 3 + 1], mVert[i * 3 + 2]);
_mSignalCv.wait(lock, [this]{ return (mIsRunning || mState == EModuleState::Trace); });
} }
if (mToTrace.size() == 0) std::vector<Triangle> triangles(mIc / 3);
{ for (size_t i = 0; i < triangles.size(); ++i) {
lock.unlock(); triangles[i].indices[0] = mIndices[i * 3];
mState = EModuleState::Idle; triangles[i].indices[1] = mIndices[i * 3 + 1];
_mDoneCv.notify_all(); triangles[i].indices[2] = mIndices[i * 3 + 2];
continue;
} }
rootNode = buildKDTree(vertices, triangles);
yolo::info(mLogModule, "Accelerator ready..");
mState = EModuleState::Idle;
}
void updateTris() override {}
void start() override
{
std::lock_guard<std::mutex> signalLock(_mSignalMut);
mIsRunning = true;
mState = EModuleState::Trace; mState = EModuleState::Trace;
_mSignalCv.notify_all();
Ray* ray = mToTrace.front(); yolo::info(mLogModule, "Signal master to start");
int bestIdx = -1;
glm::vec2 coords;
glm::vec2 bestTexcoord;
float bestDist = INFINITY;
float dist;
// Traverse the K-D tree to identify the set of triangles that may intersect the ray.
std::vector<uint32_t> candidateIndices;
mKdTree->intersect(ray, candidateIndices);
for (uint32_t idx : candidateIndices)
{ {
uint32_t ind1 = mIndices[idx * 3]; std::unique_lock<std::mutex> doneLock(_mDoneMut);
uint32_t ind2 = mIndices[idx * 3 + 1]; _mDoneCv.wait(doneLock, [this] { return mState == EModuleState::Idle; });
uint32_t ind3 = mIndices[idx * 3 + 2];
const glm::vec3 a = { mVert[ind1], mVert[ind1 + 1], mVert[ind1 + 2] };
const glm::vec3 b = { mVert[ind2], mVert[ind2 + 1], mVert[ind2 + 2] };
const glm::vec3 c = { mVert[ind3], mVert[ind3 + 1], mVert[ind3 + 2] };
// Perform intersection test...
if (!glm::intersectRayTriangle(ray->Origin, ray->Direction, a, b, c, coords, dist)) { continue; }
if (dist > bestDist || dist < 0.0f) { continue; }
bestIdx = idx;
bestDist = dist;
bestTexcoord = coords;
} }
HitInfo hit;
hit.Caller = ray;
// If no hit, we still need to inform the HHM
if (bestIdx < 0)
{
mToTrace.pop();
continue;
}
hit.Distance = bestDist;
hit.UV = bestTexcoord;
Hit(mCtx, &hit);
mToTrace.pop();
} }
}
private: void stop(bool interrupt) override
// Signaling Stuffs {
std::atomic<bool> mIsRunning; if (!interrupt)
std::thread mMasterWorker; {
std::mutex _mSignalMut; mIsRunning = false;
std::mutex _mDoneMut; return;
std::condition_variable _mSignalCv; }
std::condition_variable _mDoneCv; // TODO: Find a way to force the thread to hault
}
private: void intersectMasterWorker()
// Scene Data {
KDTree* mKdTree; for (;;)
{
std::unique_lock<std::mutex> lock(_mData);
if (!mIsRunning)
{
_mSignalCv.wait(lock, [this]{ return (mIsRunning || mState == EModuleState::Trace); });
}
float* mVert; if (mToTrace.size() == 0)
float* mNorm; {
int mVc; lock.unlock();
uint32_t* mIndices; mState = EModuleState::Idle;
int mIc; _mDoneCv.notify_all();
continue;
}
mState = EModuleState::Trace;
uint8_t mLogModule; Ray* ray = mToTrace.front();
int bestIdx = -1;
glm::vec2 coords;
glm::vec2 bestTexcoord;
float bestDist = INFINITY;
float dist;
// ...
// (Keep the existing implementation of intersectMasterWorker, but replace the KDTree intersection part)
std::vector<Triangle> candidateTriangles;
intersectKDTree(rootNode, ray->Origin, ray->Direction, candidateTriangles);
for (const Triangle& triangle : candidateTriangles)
{
uint32_t ind1 = triangle.indices[0];
uint32_t ind2 = triangle.indices[1];
uint32_t ind3 = triangle.indices[2];
const glm::vec3 a = { mVert[ind1 * 3], mVert[ind1 * 3 + 1], mVert[ind1 * 3 + 2] };
const glm::vec3 b = { mVert[ind2 * 3], mVert[ind2 * 3 + 1], mVert[ind2 * 3 + 2] };
const glm::vec3 c = { mVert[ind3 * 3], mVert[ind3 * 3 + 1], mVert[ind3 * 3 + 2] };
// Perform intersection test...
if (!glm::intersectRayTriangle(ray->Origin, ray->Direction, a, b, c, coords, dist)) { continue; }
if (dist > bestDist || dist < 0.0f) { continue; }
//bestIdx = ;
bestDist = dist;
bestTexcoord = coords;
}
HitInfo hit;
hit.Caller = ray;
// If no hit, we still need to inform the HHM
if (bestIdx < 0)
{
mToTrace.pop();
continue;
}
hit.Distance = bestDist;
hit.UV = bestTexcoord;
Hit(mCtx, &hit);
mToTrace.pop();
}
}
private:
// Signaling Stuffs
std::atomic<bool> mIsRunning;
std::thread mMasterWorker;
std::mutex _mSignalMut;
std::mutex _mDoneMut;
std::condition_variable _mSignalCv;
std::condition_variable _mDoneCv;
private:
KDNode* rootNode;
float* mVert;
float* mNorm;
int mVc;
uint32_t* mIndices;
int mIc;
uint8_t mLogModule;
}; };
HART_INTERFACE void* _GET() HART_INTERFACE void* _GET()
@@ -185,10 +193,11 @@ HART_INTERFACE void* _CREDIT()
{ {
return new ModuleCredit { return new ModuleCredit {
.ModuleName = "HART_CPU", .ModuleName = "HART_CPU",
.AuthorName = "Ben Kyd", .AuthorName = "Ben Kyd",
.ModuleDesc = "Accelerating inferno raytracing with CPU", .ModuleDesc = "Accelerating inferno raytracing with CPU",
.VersionMajor = 0, .VersionMajor = 0,
.VersionMinor = 0, .VersionMinor = 0,
.VersionBuild = 1, .VersionBuild = 1,
}; };
} }