From 350b62f986f605bb1a61558db706c9f2dd9b9b1d Mon Sep 17 00:00:00 2001 From: William Ho Date: Tue, 10 Oct 2017 13:09:43 -0400 Subject: [PATCH 01/15] code added to vertexTransformandAssembly --- src/rasterize.cu | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/rasterize.cu b/src/rasterize.cu index 1262a09..2283605 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -633,12 +633,25 @@ void _vertexTransformAndAssembly( // vertex id int vid = (blockIdx.x * blockDim.x) + threadIdx.x; if (vid < numVertices) { + VertexIndex vertexIndex = primitive.dev_indices[vid]; + VertexOut &ref_vs_output = primitive.dev_verticesOut[vertexIndex]; + + ref_vs_output.texcoord0 = primitive.dev_texcoord0[vertexIndex]; + ref_vs_output.pos = glm::vec4(primitive.dev_position[vertexIndex], 1.0f); + glm::vec3 NDCpos = glm::vec3(ref_vs_output.pos); + NDCpos.x = (NDCpos.x - (width / 2)) / (float)(width / 2); + NDCpos.y = ((height / 2) - NDCpos.y) / (float)(height / 2); + ref_vs_output.eyePos = NDCpos; + ref_vs_output.eyeNor = primitive.dev_normal[vertexIndex]; + ref_vs_output.dev_diffuseTex = primitive.dev_diffuseTex; // TODO: Apply vertex transformation here // Multiply the MVP matrix for each vertex position, this will transform everything into clipping space // Then divide the pos by its w element to transform into NDC space // Finally transform x and y to viewport space + //basic + // TODO: Apply vertex assembly here // Assemble all attribute arraies into the primitive array From 5f2c681514f71599c50940a504802e22a6ec6df1 Mon Sep 17 00:00:00 2001 From: William Ho Date: Thu, 12 Oct 2017 12:38:51 -0400 Subject: [PATCH 02/15] red rectangle rendered and barycentric functions added --- src/rasterize.cu | 84 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/src/rasterize.cu b/src/rasterize.cu index 2283605..75e0a8a 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -633,17 +633,17 @@ void _vertexTransformAndAssembly( // vertex id int vid = (blockIdx.x * blockDim.x) + threadIdx.x; if (vid < numVertices) { - VertexIndex vertexIndex = primitive.dev_indices[vid]; - VertexOut &ref_vs_output = primitive.dev_verticesOut[vertexIndex]; - - ref_vs_output.texcoord0 = primitive.dev_texcoord0[vertexIndex]; - ref_vs_output.pos = glm::vec4(primitive.dev_position[vertexIndex], 1.0f); + //VertexIndex vertexIndex = primitive.dev_indices[vid]; + VertexOut &ref_vs_output = primitive.dev_verticesOut[vid]; + // + //primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; + ref_vs_output.pos = glm::vec4(primitive.dev_position[vid], 1.0f); glm::vec3 NDCpos = glm::vec3(ref_vs_output.pos); NDCpos.x = (NDCpos.x - (width / 2)) / (float)(width / 2); NDCpos.y = ((height / 2) - NDCpos.y) / (float)(height / 2); ref_vs_output.eyePos = NDCpos; - ref_vs_output.eyeNor = primitive.dev_normal[vertexIndex]; - ref_vs_output.dev_diffuseTex = primitive.dev_diffuseTex; + //ref_vs_output.eyeNor = primitive.dev_normal[vid]; + //ref_vs_output.dev_diffuseTex = primitive.dev_diffuseTex; // TODO: Apply vertex transformation here // Multiply the MVP matrix for each vertex position, this will transform everything into clipping space @@ -673,12 +673,12 @@ void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_ // TODO: uncomment the following code for a start // This is primitive assembly for triangles - //int pid; // id for cur primitives vector - //if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { - // pid = iid / (int)primitive.primitiveType; - // dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] - // = primitive.dev_verticesOut[primitive.dev_indices[iid]]; - //} + int pid; // id for cur primitives vector + if (primitive.primitiveMode == TINYGLTF_MODE_TRIANGLES) { + pid = iid / (int)primitive.primitiveType; + dev_primitives[pid + curPrimitiveBeginId].v[iid % (int)primitive.primitiveType] + = primitive.dev_verticesOut[primitive.dev_indices[iid]]; + } // TODO: other primitive types (point, line) @@ -686,7 +686,50 @@ void _primitiveAssembly(int numIndices, int curPrimitiveBeginId, Primitive* dev_ } +__device__ +glm::vec3 getBarycentricWeights(glm::vec3 p, glm::vec3 p1, glm::vec3 p2, glm::vec3 p3) { + float totalArea = glm::length(glm::cross(p1 - p3, p2 - p3)) / 2.0f; + + float area1 = glm::length(glm::cross(p2 - p, p3 - p)) / 2.0f; + float area2 = glm::length(glm::cross(p3 - p, p1 - p)) / 2.0f; + float area3 = glm::length(glm::cross(p1 - p, p2 - p)) / 2.0f; + + return glm::vec3(area1 / totalArea, area2 / totalArea, area3 / totalArea); +} +__device__ +bool isInTriangle(glm::vec3 p, glm::vec3 p1 , glm::vec3 p2 , glm::vec3 p3 ) { + glm::vec3 bw = getBarycentricWeights(p, p1, p2, p3); + return bw.x + bw.y + bw.z > 1.0f; +} + +__global__ +void rasterizeTriangles(int numPrimitives, + int width, + int height, + Primitive* dev_primitives, + Fragment* dev_fragmentBuffer) { + int primId = (blockIdx.x * blockDim.x) + threadIdx.x; + printf("here\n"); + if (primId == 0) { + for (int x = 300; x < 500; x++) { + for (int y = 300; y < 500; y++) { + dev_fragmentBuffer[x + y * width].color = glm::vec3(0.9f, 0.0f, 0.0f); + } + } + } + +} +__global__ +void redFragments(int width, int height, Fragment* dev_fragmentBuffer) { + int fragX = (blockIdx.x * blockDim.x) + threadIdx.x; + int fragY = (blockIdx.y * blockDim.y) + threadIdx.y; + + int index = (width - fragX) + (height - fragY) * width; + if (index < width * height) { + dev_fragmentBuffer[index].color = glm::vec3((float) fragX / (float) width, (float) fragY / (float) height, 0.0f); + } +} /** * Perform rasterization. @@ -734,9 +777,20 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); initDepth << > >(width, height, dev_depth); - - // TODO: rasterize + dim3 threadsPerBlock(128); + dim3 numBlocksForRasterization((curPrimitiveBeginId + threadsPerBlock.x - 1) / threadsPerBlock.x); + + //TODO: rasterize + rasterizeTriangles << <1, 32 >> > + (curPrimitiveBeginId, + width, + height, + dev_primitives, + dev_fragmentBuffer); + + //redFragments << > > (width, height, dev_fragmentBuffer); + checkCUDAError("rasterization"); // Copy depthbuffer colors into framebuffer From 1c96e6f33eb9379201c4af37ed7b41269671e44b Mon Sep 17 00:00:00 2001 From: William Ho Date: Thu, 12 Oct 2017 13:20:29 -0400 Subject: [PATCH 03/15] crashing on isInTriangle test --- src/rasterize.cu | 61 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 9 deletions(-) diff --git a/src/rasterize.cu b/src/rasterize.cu index 75e0a8a..fc826f6 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -639,8 +639,8 @@ void _vertexTransformAndAssembly( //primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; ref_vs_output.pos = glm::vec4(primitive.dev_position[vid], 1.0f); glm::vec3 NDCpos = glm::vec3(ref_vs_output.pos); - NDCpos.x = (NDCpos.x - (width / 2)) / (float)(width / 2); - NDCpos.y = ((height / 2) - NDCpos.y) / (float)(height / 2); + //NDCpos.x = (NDCpos.x - (width / 2)) / (float)(width / 2); + //NDCpos.y = ((height / 2) - NDCpos.y) / (float)(height / 2); ref_vs_output.eyePos = NDCpos; //ref_vs_output.eyeNor = primitive.dev_normal[vid]; //ref_vs_output.dev_diffuseTex = primitive.dev_diffuseTex; @@ -699,8 +699,43 @@ glm::vec3 getBarycentricWeights(glm::vec3 p, glm::vec3 p1, glm::vec3 p2, glm::ve __device__ bool isInTriangle(glm::vec3 p, glm::vec3 p1 , glm::vec3 p2 , glm::vec3 p3 ) { - glm::vec3 bw = getBarycentricWeights(p, p1, p2, p3); - return bw.x + bw.y + bw.z > 1.0f; + float totalArea = glm::length(glm::cross(p1 - p3, p2 - p3)) / 2.0f; + + float area1 = glm::length(glm::cross(p2 - p, p3 - p)) / 2.0f; + float area2 = glm::length(glm::cross(p3 - p, p1 - p)) / 2.0f; + float area3 = glm::length(glm::cross(p1 - p, p2 - p)) / 2.0f; + + glm::vec3 bw = glm::vec3(area1 / totalArea, area2 / totalArea, area3 / totalArea); + + return (bw.x + bw.y + bw.z) > 1.0f; +} + +/** +* Computes the axis-aligned bounding box for a given prim +* Outputs: +* glm::ivec4 Contains pixel coordinates for left bottom and top right corners of AABB +*/ +__device__ +glm::ivec4 computeAABB(int width, int height, Primitive prim) { + float maxX = fmaxf(prim.v[0].pos.x, fmaxf(prim.v[1].pos.x, prim.v[2].pos.x)); + float maxY = fmaxf(prim.v[0].pos.y, fmaxf(prim.v[1].pos.y, prim.v[2].pos.y)); + float minX = fminf(prim.v[0].pos.x, fminf(prim.v[1].pos.x, prim.v[2].pos.x)); + float minY = fminf(prim.v[0].pos.y, fminf(prim.v[1].pos.y, prim.v[2].pos.y)); + + return glm::vec4( + (int) (minX * (width / 2)), + (int) (minY * (height / 2)), + (int) (maxX * (width / 2)), + (int) (maxY * (height / 2)) + ); +} + +/** +* Converts pixel coordinates to fragment index +*/ +__device__ +int pixelToFragIndex(int x, int y, int width, int height) { + return (width * height) - (x + y * width + ((width * height) / 2) - width / 2); } __global__ @@ -710,11 +745,19 @@ void rasterizeTriangles(int numPrimitives, Primitive* dev_primitives, Fragment* dev_fragmentBuffer) { int primId = (blockIdx.x * blockDim.x) + threadIdx.x; - printf("here\n"); - if (primId == 0) { - for (int x = 300; x < 500; x++) { - for (int y = 300; y < 500; y++) { - dev_fragmentBuffer[x + y * width].color = glm::vec3(0.9f, 0.0f, 0.0f); + if (primId < numPrimitives) { + Primitive primitive = dev_primitives[primId]; + glm::ivec4 AABB = computeAABB(width, height, primitive); + glm::vec3 p1 = glm::vec3(primitive.v[0].pos); + glm::vec3 p2 = glm::vec3(primitive.v[1].pos); + glm::vec3 p3 = glm::vec3(primitive.v[2].pos); + + for (int y = AABB.y; y < AABB.w; y++) { + for (int x = AABB.x; x < AABB.z; x++) { + if (isInTriangle(glm::vec3(x, y, 0), p1, p2, p3)) { + int fragIndex = pixelToFragIndex(x, y, width, height); + dev_fragmentBuffer[fragIndex].color = glm::vec3(0.9f, 0.0f, 0.0f); + } } } } From 081fad4937d935a6bc98174db8d335a42fbed887 Mon Sep 17 00:00:00 2001 From: William Ho Date: Fri, 13 Oct 2017 14:02:38 -0400 Subject: [PATCH 04/15] basic NDC to spixel works --- src/main.cpp | 4 ++-- src/rasterize.cu | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7986959..b4e49e6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,8 +138,8 @@ bool init(const tinygltf::Scene & scene) { return false; } - width = 800; - height = 800; + width = 200; + height = 200; window = glfwCreateWindow(width, height, "CIS 565 Pathtracer", NULL, NULL); if (!window) { glfwTerminate(); diff --git a/src/rasterize.cu b/src/rasterize.cu index fc826f6..d25522b 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -638,6 +638,7 @@ void _vertexTransformAndAssembly( // //primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; ref_vs_output.pos = glm::vec4(primitive.dev_position[vid], 1.0f); + printf("%f, %f, %f, %f\n", ref_vs_output.pos.x, ref_vs_output.pos.y, ref_vs_output.pos.z, ref_vs_output.pos.w); glm::vec3 NDCpos = glm::vec3(ref_vs_output.pos); //NDCpos.x = (NDCpos.x - (width / 2)) / (float)(width / 2); //NDCpos.y = ((height / 2) - NDCpos.y) / (float)(height / 2); @@ -699,15 +700,13 @@ glm::vec3 getBarycentricWeights(glm::vec3 p, glm::vec3 p1, glm::vec3 p2, glm::ve __device__ bool isInTriangle(glm::vec3 p, glm::vec3 p1 , glm::vec3 p2 , glm::vec3 p3 ) { - float totalArea = glm::length(glm::cross(p1 - p3, p2 - p3)) / 2.0f; - - float area1 = glm::length(glm::cross(p2 - p, p3 - p)) / 2.0f; - float area2 = glm::length(glm::cross(p3 - p, p1 - p)) / 2.0f; - float area3 = glm::length(glm::cross(p1 - p, p2 - p)) / 2.0f; - + float totalArea = glm::abs((p1.x * (p2.y - p3.y) + p2.x * (p3.y - p1.y) + p3.x * (p1.y - p2.y)) / 2.0f); + + float area1 = glm::abs((p.x * (p2.y - p3.y) + p2.x * (p3.y - p.y) + p3.x * (p.y - p2.y)) / 2.0f); + float area2 = glm::abs((p1.x * (p.y - p3.y) + p.x * (p3.y - p1.y) + p3.x * (p1.y - p.y)) / 2.0f); + float area3 = glm::abs((p1.x * (p2.y - p.y) + p2.x * (p.y - p1.y) + p.x * (p1.y - p2.y)) / 2.0f); glm::vec3 bw = glm::vec3(area1 / totalArea, area2 / totalArea, area3 / totalArea); - - return (bw.x + bw.y + bw.z) > 1.0f; + return !((bw.x + bw.y + bw.z) > 1.00001f); } /** @@ -723,10 +722,10 @@ glm::ivec4 computeAABB(int width, int height, Primitive prim) { float minY = fminf(prim.v[0].pos.y, fminf(prim.v[1].pos.y, prim.v[2].pos.y)); return glm::vec4( - (int) (minX * (width / 2)), - (int) (minY * (height / 2)), - (int) (maxX * (width / 2)), - (int) (maxY * (height / 2)) + (int) ((minX + 1.0f) * (width / 2)), + (int) ((1.0f - maxY) * (height / 2)), + (int) ((maxX + 1.0f) * (width / 2)), + (int) ((1.0f - minY) * (height / 2)) ); } @@ -735,7 +734,12 @@ glm::ivec4 computeAABB(int width, int height, Primitive prim) { */ __device__ int pixelToFragIndex(int x, int y, int width, int height) { - return (width * height) - (x + y * width + ((width * height) / 2) - width / 2); + return y * width - x; +} + +__device__ +glm::vec3 NDCtoPixel(glm::vec3 p, int width, int height) { + return glm::vec3((p.x + 1.0f) * (width / 2), (1.0f - p.y) * (height / 2), p.z); } __global__ @@ -746,15 +750,15 @@ void rasterizeTriangles(int numPrimitives, Fragment* dev_fragmentBuffer) { int primId = (blockIdx.x * blockDim.x) + threadIdx.x; if (primId < numPrimitives) { - Primitive primitive = dev_primitives[primId]; + Primitive& primitive = dev_primitives[primId]; glm::ivec4 AABB = computeAABB(width, height, primitive); - glm::vec3 p1 = glm::vec3(primitive.v[0].pos); - glm::vec3 p2 = glm::vec3(primitive.v[1].pos); - glm::vec3 p3 = glm::vec3(primitive.v[2].pos); - + glm::vec3 pPix1 = NDCtoPixel(glm::vec3(primitive.v[0].pos), width, height); + glm::vec3 pPix2 = NDCtoPixel(glm::vec3(primitive.v[1].pos), width, height); + glm::vec3 pPix3 = NDCtoPixel(glm::vec3(primitive.v[2].pos), width, height); for (int y = AABB.y; y < AABB.w; y++) { for (int x = AABB.x; x < AABB.z; x++) { - if (isInTriangle(glm::vec3(x, y, 0), p1, p2, p3)) { + //Something is wrong with the conversion in the Axis aligne bounding box. These aren't printing out correct numbers: + if (isInTriangle(glm::vec3(x, y, 0.0f), pPix1, pPix2, pPix3)) { int fragIndex = pixelToFragIndex(x, y, width, height); dev_fragmentBuffer[fragIndex].color = glm::vec3(0.9f, 0.0f, 0.0f); } @@ -825,7 +829,7 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dim3 numBlocksForRasterization((curPrimitiveBeginId + threadsPerBlock.x - 1) / threadsPerBlock.x); //TODO: rasterize - rasterizeTriangles << <1, 32 >> > + rasterizeTriangles << > > (curPrimitiveBeginId, width, height, From e83f993e7886c9f0515f715f19a6b527e6155bf1 Mon Sep 17 00:00:00 2001 From: William Ho Date: Fri, 13 Oct 2017 23:49:21 -0400 Subject: [PATCH 05/15] basic rasterizer works, but lambertian shading for some reason does not --- src/main.cpp | 4 +- src/rasterize.cu | 112 ++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 98 insertions(+), 18 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b4e49e6..eff8d62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,8 +138,8 @@ bool init(const tinygltf::Scene & scene) { return false; } - width = 200; - height = 200; + width = 400; + height = 400; window = glfwCreateWindow(width, height, "CIS 565 Pathtracer", NULL, NULL); if (!window) { glfwTerminate(); diff --git a/src/rasterize.cu b/src/rasterize.cu index d25522b..7d19a73 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -103,6 +103,7 @@ static std::map> mesh2Primitiv static int width = 0; static int height = 0; +static float depthRange = 5000000.0f; static int totalNumPrimitives = 0; static Primitive *dev_primitives = NULL; @@ -110,6 +111,7 @@ static Fragment *dev_fragmentBuffer = NULL; static glm::vec3 *dev_framebuffer = NULL; static int * dev_depth = NULL; // you might need this buffer when doing depth test +static int * mutex = NULL; /** * Kernel that writes the image to the OpenGL PBO directly. @@ -166,6 +168,9 @@ void rasterizeInit(int w, int h) { cudaFree(dev_depth); cudaMalloc(&dev_depth, width * height * sizeof(int)); + cudaFree(mutex); + cudaMalloc(&mutex, sizeof(int)); + checkCUDAError("rasterizeInit"); } @@ -637,12 +642,11 @@ void _vertexTransformAndAssembly( VertexOut &ref_vs_output = primitive.dev_verticesOut[vid]; // //primitive.dev_verticesOut[vid].texcoord0 = primitive.dev_texcoord0[vid]; - ref_vs_output.pos = glm::vec4(primitive.dev_position[vid], 1.0f); - printf("%f, %f, %f, %f\n", ref_vs_output.pos.x, ref_vs_output.pos.y, ref_vs_output.pos.z, ref_vs_output.pos.w); - glm::vec3 NDCpos = glm::vec3(ref_vs_output.pos); - //NDCpos.x = (NDCpos.x - (width / 2)) / (float)(width / 2); - //NDCpos.y = ((height / 2) - NDCpos.y) / (float)(height / 2); - ref_vs_output.eyePos = NDCpos; + glm::vec4 pos = glm::vec4(primitive.dev_position[vid], 1.0f); + ref_vs_output.pos = MVP * pos; + ref_vs_output.eyePos = glm::vec3(MV * pos); + ref_vs_output.eyeNor = glm::vec3(MV_normal * primitive.dev_normal[vid]); + //ref_vs_output.eyeNor = primitive.dev_normal[vid]; //ref_vs_output.dev_diffuseTex = primitive.dev_diffuseTex; @@ -696,7 +700,7 @@ glm::vec3 getBarycentricWeights(glm::vec3 p, glm::vec3 p1, glm::vec3 p2, glm::ve float area3 = glm::length(glm::cross(p1 - p, p2 - p)) / 2.0f; return glm::vec3(area1 / totalArea, area2 / totalArea, area3 / totalArea); -} +} __device__ bool isInTriangle(glm::vec3 p, glm::vec3 p1 , glm::vec3 p2 , glm::vec3 p3 ) { @@ -722,10 +726,10 @@ glm::ivec4 computeAABB(int width, int height, Primitive prim) { float minY = fminf(prim.v[0].pos.y, fminf(prim.v[1].pos.y, prim.v[2].pos.y)); return glm::vec4( - (int) ((minX + 1.0f) * (width / 2)), - (int) ((1.0f - maxY) * (height / 2)), - (int) ((maxX + 1.0f) * (width / 2)), - (int) ((1.0f - minY) * (height / 2)) + (int) ((minX + 1.0f) * (width / 2)) - 1, + (int) ((1.0f - maxY) * (height / 2)) - 1, //Necessary to flip max and min Y because in pixel space, 0,0 is the top left + (int) ((maxX + 1.0f) * (width / 2)) + 1, + (int) ((1.0f - minY) * (height / 2) + 1) ); } @@ -742,12 +746,47 @@ glm::vec3 NDCtoPixel(glm::vec3 p, int width, int height) { return glm::vec3((p.x + 1.0f) * (width / 2), (1.0f - p.y) * (height / 2), p.z); } +/* Takes in information in NDC and outputs z-depth +*/ + +__device__ +float computeFragmentDepth(glm::vec3 p, Primitive prim) { + glm::vec3 p1 = glm::vec3(prim.v[0].pos); + glm::vec3 p2 = glm::vec3(prim.v[1].pos); + glm::vec3 p3 = glm::vec3(prim.v[2].pos); + + p1.z = 0.0f; + p2.z = 0.0f; + p3.z = 0.0f; + + glm::vec3 eyePos1 = glm::vec3(prim.v[0].eyePos); + glm::vec3 eyePos2 = glm::vec3(prim.v[1].eyePos); + glm::vec3 eyePos3 = glm::vec3(prim.v[2].eyePos); + + float totalArea = glm::length(glm::cross(p1 - p3, p2 - p3)) / 2.0f; + + float area1 = glm::length(glm::cross(p2 - p, p3 - p)) / 2.0f; + float area2 = glm::length(glm::cross(p1 - p, p3 - p)) / 2.0f; + float area3 = glm::length(glm::cross(p1 - p, p2 - p)) / 2.0f; + glm::vec3 bw = glm::vec3(area1 / totalArea, area2 / totalArea, area3 / totalArea); + + return 1.0f / ((1.0f / eyePos1.z) * bw.x + (1.0f / eyePos2.z) * bw.y + (1.0f / eyePos3.z) * bw.z); +} + +__device__ +glm::vec3 computeLambertian(glm::vec3 normal, glm::vec3 light, glm::vec3 baseColor) { + return fmaxf(glm::dot(glm::normalize(normal), glm::normalize(light)), 0.1f) * baseColor; +} + __global__ void rasterizeTriangles(int numPrimitives, int width, int height, Primitive* dev_primitives, - Fragment* dev_fragmentBuffer) { + Fragment* dev_fragmentBuffer, + int * dev_depth, + float depthRange, + int * mutex) { int primId = (blockIdx.x * blockDim.x) + threadIdx.x; if (primId < numPrimitives) { Primitive& primitive = dev_primitives[primId]; @@ -755,18 +794,47 @@ void rasterizeTriangles(int numPrimitives, glm::vec3 pPix1 = NDCtoPixel(glm::vec3(primitive.v[0].pos), width, height); glm::vec3 pPix2 = NDCtoPixel(glm::vec3(primitive.v[1].pos), width, height); glm::vec3 pPix3 = NDCtoPixel(glm::vec3(primitive.v[2].pos), width, height); + glm::vec3 tri[3] = { + pPix1, + pPix2, + pPix3 }; for (int y = AABB.y; y < AABB.w; y++) { for (int x = AABB.x; x < AABB.z; x++) { - //Something is wrong with the conversion in the Axis aligne bounding box. These aren't printing out correct numbers: - if (isInTriangle(glm::vec3(x, y, 0.0f), pPix1, pPix2, pPix3)) { + glm::vec3 bw = calculateBarycentricCoordinate(tri, glm::vec2(x, y)); + if (isBarycentricCoordInBounds(bw)) { int fragIndex = pixelToFragIndex(x, y, width, height); - dev_fragmentBuffer[fragIndex].color = glm::vec3(0.9f, 0.0f, 0.0f); + //printf("%f\n", computeFragmentDepth(glm::vec3(x, y, 0.0f), primitive)); + int depth = (int) (depthRange * -getZAtCoordinate(bw, tri)); + //atomicMin(dev_depth + fragIndex, depth); + //if (depth == dev_depth[fragIndex]) dev_fragmentBuffer[fragIndex].color = glm::vec3((float) primId / (float) numPrimitives, 0.0f, 0.0f); + //printf("%i\n", depth); + bool isSet; + do { + isSet = (atomicCAS(mutex, 0, 1) == 0); + if (isSet) { + dev_depth[fragIndex] = min(dev_depth[fragIndex], depth); + if (depth == dev_depth[fragIndex]) dev_fragmentBuffer[fragIndex].color = primitive.v[0].eyeNor; + + } + if (isSet) { + *mutex = 0; + } + } while (!isSet); } } } } } + +__global__ +void shadeLambertian(int numFragments, Fragment* dev_fragmentBuffer, glm::vec3 light) { + int idx = (blockIdx.x * blockDim.x) + threadIdx.x; + if (idx < numFragments) { + printf("%i\n", numFragments); + dev_fragmentBuffer[idx].color = glm::vec3(1.0f, 0.5f, 0.5f); + } +} __global__ void redFragments(int width, int height, Fragment* dev_fragmentBuffer) { int fragX = (blockIdx.x * blockDim.x) + threadIdx.x; @@ -827,6 +895,10 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dim3 threadsPerBlock(128); dim3 numBlocksForRasterization((curPrimitiveBeginId + threadsPerBlock.x - 1) / threadsPerBlock.x); + + cudaMemset(mutex, 0, sizeof(int)); + + glm::vec3 light = glm::vec3(1.0f, 1.0f, 1.0f); //TODO: rasterize rasterizeTriangles << > > @@ -834,7 +906,15 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g width, height, dev_primitives, - dev_fragmentBuffer); + dev_fragmentBuffer, + dev_depth, + depthRange, + mutex); + + shadeLambertian << > > ( + width * height, + dev_fragmentBuffer, + light); //redFragments << > > (width, height, dev_fragmentBuffer); checkCUDAError("rasterization"); From 4fd779e68518ff5ca654ecc350189aedfcd925b6 Mon Sep 17 00:00:00 2001 From: William Ho Date: Sun, 15 Oct 2017 17:37:21 -0400 Subject: [PATCH 06/15] tile doesn't work, but it's in there --- src/main.cpp | 4 +- src/rasterize.cu | 150 +++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 147 insertions(+), 7 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index eff8d62..9ae29ed 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,8 +138,8 @@ bool init(const tinygltf::Scene & scene) { return false; } - width = 400; - height = 400; + width = 100; + height = 100; window = glfwCreateWindow(width, height, "CIS 565 Pathtracer", NULL, NULL); if (!window) { glfwTerminate(); diff --git a/src/rasterize.cu b/src/rasterize.cu index 7d19a73..798b79b 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -18,6 +18,12 @@ #include #include +#define TILERENDER 1 +//These need to be defined at compile time, but they need to be mathematically sound. TILEX * TILEY = TILESIZE +#define TILEX 25 +#define TILEY 25 +#define TILESIZE 625 + namespace { typedef unsigned short VertexIndex; @@ -725,7 +731,7 @@ glm::ivec4 computeAABB(int width, int height, Primitive prim) { float minX = fminf(prim.v[0].pos.x, fminf(prim.v[1].pos.x, prim.v[2].pos.x)); float minY = fminf(prim.v[0].pos.y, fminf(prim.v[1].pos.y, prim.v[2].pos.y)); - return glm::vec4( + return glm::ivec4( (int) ((minX + 1.0f) * (width / 2)) - 1, (int) ((1.0f - maxY) * (height / 2)) - 1, //Necessary to flip max and min Y because in pixel space, 0,0 is the top left (int) ((maxX + 1.0f) * (width / 2)) + 1, @@ -827,6 +833,115 @@ void rasterizeTriangles(int numPrimitives, } +__device__ +int tileIndexToFragIndex(int tileIndex, int tileX, glm::ivec4 tile, int width, int height) { + int tileYcoord = tileIndex / tileX; + int tileXcoord = tileIndex - (tileYcoord * tileX); + //printf("%i, %i, %i\n", tileIndex, tileX, tileYcoord); + + return pixelToFragIndex(tile.x + tileXcoord, tile.y + tileYcoord, width, height); +} + +__global__ +void tileRasterizeTriangles(int numPrimitives, + int width, + int height, + Primitive* dev_primitives, + Fragment* dev_fragmentBuffer, + int * dev_depth, + float depthRange, + int * mutex, + int tileX, + int tileY) { + //Allocate shared memory for tile + //Format: x,y,z is color and w is depth + // On Moore 100 Machines, max shared memory is c000 (49152) bytes, + // with this, shared memory usage is 40000 bytes + __shared__ glm::vec4 shared_tileBuffer[TILESIZE]; + int sharedWritesPerThread = (TILESIZE + blockDim.x - 1) / blockDim.x; + for (int i = 0; i < sharedWritesPerThread; i++) { + if (i + threadIdx.x * sharedWritesPerThread < TILESIZE) { + shared_tileBuffer[i + threadIdx.x * sharedWritesPerThread] = glm::vec4(0.0f, 0.0f, 0.0f, FLT_MAX); + } + } + __syncthreads(); + glm::ivec4 tile = glm::ivec4(tileX * TILEX, tileY * TILEY, (tileX + 1) * TILEX, (tileY + 1) * TILEY); + int primId = threadIdx.x; + if (primId < numPrimitives) { + //Initialize shared memory + //Evaluate bounding box and determine if the triangle needs to be rendered in this tile + Primitive& primitive = dev_primitives[primId]; + glm::ivec4 AABB = computeAABB(width, height, primitive); + if (AABB.x > tile.z || AABB.z < tile.x || AABB.y > tile.w || AABB.w < tile.y) { + return; + } + + glm::vec3 pPix1 = NDCtoPixel(glm::vec3(primitive.v[0].pos), width, height); + glm::vec3 pPix2 = NDCtoPixel(glm::vec3(primitive.v[1].pos), width, height); + glm::vec3 pPix3 = NDCtoPixel(glm::vec3(primitive.v[2].pos), width, height); + glm::vec3 tri[3] = { + pPix1, + pPix2, + pPix3 }; + for (int y = AABB.y; y < AABB.w; y++) { + for (int x = AABB.x; x < AABB.z; x++) { + //Test if pixel x,y is in the triangle + glm::vec3 bw = calculateBarycentricCoordinate(tri, glm::vec2(x, y)); + if (isBarycentricCoordInBounds(bw)) { + //Convert the pixel to a fragmentIndex and compute its depth + int fragIndex = pixelToFragIndex(x, y, width, height); + int fragTileIndex = (x - tile.x) + (y - tile.y) * TILEY; + printf("%i, %i, %i\n", fragTileIndex, x, y); + //Cull fragments outside of tile + if (fragTileIndex < TILESIZE && fragTileIndex > 0) { + float depth = (depthRange * -getZAtCoordinate(bw, tri)); + //depth test, the mutex ensures that the code within "isSet" happens atomically + //that is, that code is guaranteed to execute in a single thread without anything executing in + //any other thread + bool isSet; + do { + isSet = (atomicCAS(mutex, 0, 1) == 0); + if (isSet) { + /*dev_depth[fragIndex] = min(dev_depth[fragIndex], (int)depth); + if (depth == dev_depth[fragIndex]) dev_fragmentBuffer[fragIndex].color = primitive.v[0].eyeNor;*/ + float originalDepth = shared_tileBuffer[fragTileIndex].w; + shared_tileBuffer[fragTileIndex].w = fminf(originalDepth, depth); + if (depth < originalDepth) { + glm::vec3 color = primitive.v[0].eyeNor; + shared_tileBuffer[fragTileIndex].x = color.x; + shared_tileBuffer[fragTileIndex].y = color.y; + shared_tileBuffer[fragTileIndex].z = color.z; + + } + } + if (isSet) { + *mutex = 0; + } + } while (!isSet); + } + } + } + } + //This is therefore where I should call __syncThreads(); and write to the dev_fragmentBuffer + //Will need some kind of for loop to write to the dev_fragmentBuffer + } + __syncthreads(); + + //Write shared memory to fragmentbuffer. This might be more optimal if we wanted to retire threads after the above branch and split up writes + //based on the number of primitives. + for (int i = 0; i < sharedWritesPerThread; i++) { + if (i + threadIdx.x * sharedWritesPerThread < TILESIZE) { + int fragIndex = tileIndexToFragIndex(i + threadIdx.x * sharedWritesPerThread, + TILEX, + tile, + width, + height); + dev_fragmentBuffer[fragIndex].color = glm::vec3(shared_tileBuffer[i + threadIdx.x * sharedWritesPerThread]); + + } + } +} + __global__ void shadeLambertian(int numFragments, Fragment* dev_fragmentBuffer, glm::vec3 light) { int idx = (blockIdx.x * blockDim.x) + threadIdx.x; @@ -893,7 +1008,7 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g cudaMemset(dev_fragmentBuffer, 0, width * height * sizeof(Fragment)); initDepth << > >(width, height, dev_depth); - dim3 threadsPerBlock(128); + dim3 threadsPerBlock((curPrimitiveBeginId + 31) / 32); dim3 numBlocksForRasterization((curPrimitiveBeginId + threadsPerBlock.x - 1) / threadsPerBlock.x); cudaMemset(mutex, 0, sizeof(int)); @@ -901,6 +1016,31 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g glm::vec3 light = glm::vec3(1.0f, 1.0f, 1.0f); //TODO: rasterize +#if TILERENDER + tileRasterizeTriangles << <1, 128 >> > + (curPrimitiveBeginId, + width, + height, + dev_primitives, + dev_fragmentBuffer, + dev_depth, + depthRange, + mutex, + 1, + 1); + tileRasterizeTriangles << <1, 128 >> > + (curPrimitiveBeginId, + width, + height, + dev_primitives, + dev_fragmentBuffer, + dev_depth, + depthRange, + mutex, + 2, + 2); + +#else rasterizeTriangles << > > (curPrimitiveBeginId, width, @@ -910,12 +1050,12 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dev_depth, depthRange, mutex); - - shadeLambertian << > > ( +#endif + /*shadeLambertian << > > ( width * height, dev_fragmentBuffer, light); - +*/ //redFragments << > > (width, height, dev_fragmentBuffer); checkCUDAError("rasterization"); From fb5a003dd2f6ce63ccf30f4359500ef0a79ea97f Mon Sep 17 00:00:00 2001 From: William Ho Date: Mon, 16 Oct 2017 12:59:12 -0400 Subject: [PATCH 07/15] tiles correctly testing against triangles --- src/rasterize.cu | 64 +++++++++++++++++++++++++----------------------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/rasterize.cu b/src/rasterize.cu index 798b79b..c254016 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -842,6 +842,11 @@ int tileIndexToFragIndex(int tileIndex, int tileX, glm::ivec4 tile, int width, i return pixelToFragIndex(tile.x + tileXcoord, tile.y + tileYcoord, width, height); } +__device__ +bool pixelInTile(int x, int y, glm::ivec4 tile) { + return (x >= tile.x && x < tile.z && y >= tile.y && y < tile.w); +} + __global__ void tileRasterizeTriangles(int numPrimitives, int width, @@ -872,7 +877,7 @@ void tileRasterizeTriangles(int numPrimitives, //Evaluate bounding box and determine if the triangle needs to be rendered in this tile Primitive& primitive = dev_primitives[primId]; glm::ivec4 AABB = computeAABB(width, height, primitive); - if (AABB.x > tile.z || AABB.z < tile.x || AABB.y > tile.w || AABB.w < tile.y) { + if (AABB.x >= tile.z || AABB.z < tile.x || AABB.y >= tile.w || AABB.w < tile.y) { return; } @@ -887,38 +892,35 @@ void tileRasterizeTriangles(int numPrimitives, for (int x = AABB.x; x < AABB.z; x++) { //Test if pixel x,y is in the triangle glm::vec3 bw = calculateBarycentricCoordinate(tri, glm::vec2(x, y)); - if (isBarycentricCoordInBounds(bw)) { + if (isBarycentricCoordInBounds(bw) && pixelInTile(x, y, tile)) { //Convert the pixel to a fragmentIndex and compute its depth int fragIndex = pixelToFragIndex(x, y, width, height); int fragTileIndex = (x - tile.x) + (y - tile.y) * TILEY; - printf("%i, %i, %i\n", fragTileIndex, x, y); //Cull fragments outside of tile - if (fragTileIndex < TILESIZE && fragTileIndex > 0) { - float depth = (depthRange * -getZAtCoordinate(bw, tri)); - //depth test, the mutex ensures that the code within "isSet" happens atomically - //that is, that code is guaranteed to execute in a single thread without anything executing in - //any other thread - bool isSet; - do { - isSet = (atomicCAS(mutex, 0, 1) == 0); - if (isSet) { - /*dev_depth[fragIndex] = min(dev_depth[fragIndex], (int)depth); - if (depth == dev_depth[fragIndex]) dev_fragmentBuffer[fragIndex].color = primitive.v[0].eyeNor;*/ - float originalDepth = shared_tileBuffer[fragTileIndex].w; - shared_tileBuffer[fragTileIndex].w = fminf(originalDepth, depth); - if (depth < originalDepth) { - glm::vec3 color = primitive.v[0].eyeNor; - shared_tileBuffer[fragTileIndex].x = color.x; - shared_tileBuffer[fragTileIndex].y = color.y; - shared_tileBuffer[fragTileIndex].z = color.z; + float depth = (depthRange * -getZAtCoordinate(bw, tri)); + //depth test, the mutex ensures that the code within "isSet" happens atomically + //that is, that code is guaranteed to execute in a single thread without anything executing in + //any other thread + bool isSet; + do { + isSet = (atomicCAS(mutex, 0, 1) == 0); + if (isSet) { + /*dev_depth[fragIndex] = min(dev_depth[fragIndex], (int)depth); + if (depth == dev_depth[fragIndex]) dev_fragmentBuffer[fragIndex].color = primitive.v[0].eyeNor;*/ + float originalDepth = shared_tileBuffer[fragTileIndex].w; + shared_tileBuffer[fragTileIndex].w = fminf(originalDepth, depth); + if (depth < originalDepth) { + glm::vec3 color = primitive.v[0].eyeNor; + shared_tileBuffer[fragTileIndex].x = color.x; + shared_tileBuffer[fragTileIndex].y = color.y; + shared_tileBuffer[fragTileIndex].z = color.z; - } } - if (isSet) { - *mutex = 0; - } - } while (!isSet); - } + } + if (isSet) { + *mutex = 0; + } + } while (!isSet); } } } @@ -1026,8 +1028,8 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dev_depth, depthRange, mutex, - 1, - 1); + 2, + 2); tileRasterizeTriangles << <1, 128 >> > (curPrimitiveBeginId, width, @@ -1037,8 +1039,8 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dev_depth, depthRange, mutex, - 2, - 2); + 1, + 1); #else rasterizeTriangles << > > From 0f3110bf69f7386cc38f10e3de3ebaed7f61d7c4 Mon Sep 17 00:00:00 2001 From: William Ho Date: Wed, 18 Oct 2017 15:02:22 -0400 Subject: [PATCH 08/15] basic tilerenderwithPreProcess --- renders/TileBasedCubeV001.PNG | Bin 0 -> 1364 bytes src/main.cpp | 4 +- src/rasterize.cu | 176 +++++++++++++++++++++++++++++----- 3 files changed, 156 insertions(+), 24 deletions(-) create mode 100755 renders/TileBasedCubeV001.PNG diff --git a/renders/TileBasedCubeV001.PNG b/renders/TileBasedCubeV001.PNG new file mode 100755 index 0000000000000000000000000000000000000000..cbd55baf2ce0282879b269cd57e844a1eb398bec GIT binary patch literal 1364 zcmeAS@N?(olHy`uVBq!ia0vp^Wk8(5!3HFQcOGd3QjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKs7HtT^vIy;@;lz4Za;Fa;&hHc|&1?z&)Ni zW?^T}%P$w5_S^5V`E|*!le^!23VSZazVJ_KNbwTwxpR)@PkIp-d-}X^qb~O*`~SbJ zd(=M{n8voezIoRCQ~u4mO55LQI-hd0^>6Q=zj@=lYSX#LP1Qf&Hi*?x{`vb?;k@TZ zD!Z>ApLl-enbdb_|ElJHty|xpUz}4o_s;1XCNVL-hkiaSwb;FbYx(o(+2W52$|^;- zpWY#}evS5Vv)w;so^4-k5Wnuwqjz?9YU(()ubzLX=&QuE@BTaWA3l4hc;=I(QM}xH zo%rWZzSzjf)$*_}K5QVZkE8Zaneam3^^+&cxj2hgG^Wci(*f z`)k~_KaXEupWw+5InC0Ut+{!^$^SJ{C4W`ZbJgPOGA?`#*D0$iZf{<<|4DVBn!XT7 zvu%; zqwf*>mt^#dXHSXOHhc9m>Z2~-u797hkGlQbnzZ;&)W*QNwL9M2kN+1Tt+G0c2M&6q@j9^SQz z8F}1xGIH&2E=Nv2YBzslsz}oTQ>_dCVqK5lU$D4;{*xZr#|!v0Oc(7G)mRaG;Z|tI ze;}#bq5k@4MN_lYiYrW9hmFp7#Me09d2_rhAWf3>gYtnrB}cM_KV42TS;5?Or;W+r z(f2p=!!7h>x350FOkdNfGQTlk*MgWc)rE0ZVg2^)IfdUFnynV>uu{13eL+I-<2@yJ z?EhZ+vfMf#Dppq4h5Px^$N#+J-mJOE&$wX2EDauSpi$-g{=WCa&n&&_m>6-W@?}SS z>-po?EmkypuWmlHq2==>!TX0!=^t6(C>s+cE7GPqXL{d{gXb=O7oQGdmox78apf!dRn$K^=Pna-zgrg+Y~U3|8{^QDKoeLFs1dU#arNaf3n zg*UWf(ga%rLt@rvRR3YuC2Q z`CqtWRGB>^8E?JeHE(3Iuu)hiQ?x5d1ZV!dSkr{;R= #include -#define TILERENDER 1 +#define TILERENDER 0 +#define TILERENDERWITHPREPROCESS 1 //These need to be defined at compile time, but they need to be mathematically sound. TILEX * TILEY = TILESIZE -#define TILEX 25 -#define TILEY 25 -#define TILESIZE 625 +#define TILEX 32 +#define TILEY 32 +#define TILESIZE 1024 namespace { @@ -102,6 +103,13 @@ namespace { // TODO: add more attributes when needed }; +#if TILERENDERWITHPREPROCESS + struct Tile { + int numTriangles = 0; + int triangleIndices[1000]; + }; +#endif + } static std::map> mesh2PrimitivesMap; @@ -116,6 +124,11 @@ static Primitive *dev_primitives = NULL; static Fragment *dev_fragmentBuffer = NULL; static glm::vec3 *dev_framebuffer = NULL; +#if TILERENDERWITHPREPROCESS +static int *dev_triangleIndicesForFrag = NULL; +static Tile *dev_tiles = NULL; +#endif + static int * dev_depth = NULL; // you might need this buffer when doing depth test static int * mutex = NULL; @@ -177,6 +190,14 @@ void rasterizeInit(int w, int h) { cudaFree(mutex); cudaMalloc(&mutex, sizeof(int)); +#if TILERENDERWITHPREPROCESS + cudaFree(dev_triangleIndicesForFrag); + cudaMalloc(&dev_triangleIndicesForFrag, width * height * sizeof(int)); + + cudaFree(dev_tiles); + cudaMalloc(&dev_tiles, ((width + TILEX - 1) / TILEX) * ((height + TILEY - 1) / TILEY) * sizeof(Tile)); +#endif + checkCUDAError("rasterizeInit"); } @@ -855,13 +876,13 @@ void tileRasterizeTriangles(int numPrimitives, Fragment* dev_fragmentBuffer, int * dev_depth, float depthRange, - int * mutex, - int tileX, - int tileY) { + int * mutex) { //Allocate shared memory for tile //Format: x,y,z is color and w is depth // On Moore 100 Machines, max shared memory is c000 (49152) bytes, // with this, shared memory usage is 40000 bytes + int tileX = blockIdx.x; + int tileY = blockIdx.y; __shared__ glm::vec4 shared_tileBuffer[TILESIZE]; int sharedWritesPerThread = (TILESIZE + blockDim.x - 1) / blockDim.x; for (int i = 0; i < sharedWritesPerThread; i++) { @@ -939,17 +960,115 @@ void tileRasterizeTriangles(int numPrimitives, width, height); dev_fragmentBuffer[fragIndex].color = glm::vec3(shared_tileBuffer[i + threadIdx.x * sharedWritesPerThread]); + } + } +} + + +#if TILERENDERWITHPREPROCESS +__global__ +void computeTrianglesToBeRendered(int numPrimitives, + int width, + int height, + int tileGridWidth, + Primitive* dev_primitives, + Tile* dev_tiles, + int *mutex) { + //Parellelize over triangles + //Using AABB, bucket the triangles into the tiles + int primId = (blockIdx.x * blockDim.x) + threadIdx.x; + if (primId < numPrimitives) { + Primitive& primitive = dev_primitives[primId]; + glm::ivec4 AABB = computeAABB(width, height, primitive); + int minX = (AABB.x / TILEX) * TILEX; + int minY = (AABB.y / TILEY) * TILEY; + for (int y = minY; y < AABB.w; y += TILEY) { + for (int x = minX; x < AABB.z; x += TILEX) { + Tile& tile = dev_tiles[(x / TILEX) + (y / TILEY) * tileGridWidth]; + bool isSet; + do { + isSet = (atomicCAS(mutex, 0, 1) == 0); + if (isSet) { + int bucketIdx = tile.numTriangles; + tile.triangleIndices[bucketIdx] = primId; + tile.numTriangles++; + } + if (isSet) { + *mutex = 0; + } + } while (!isSet); + } + } + } + +} + +__global__ +void tileRasterizeTrianglesAfterPreProcess( + int width, + int height, + int tileGridWidth, + Primitive* dev_primitives, + Tile* dev_tiles, + Fragment* dev_fragmentBuffer) { + //Block is tile + //Thread is pixel + //Loop over the triangles in the bucket + + __shared__ glm::vec4 shared_tileBuffer[TILESIZE]; + + int tileX = blockIdx.x; + int tileY = blockIdx.y; + int pixelX = blockIdx.x * blockDim.x + threadIdx.x; + int pixelY = blockIdx.y * blockDim.y + threadIdx.y; + + int fragIdx = pixelToFragIndex(pixelX, pixelY, width, height); + Tile& tile = dev_tiles[tileX + tileY * tileGridWidth]; + int idxInTile = threadIdx.x + threadIdx.y * blockDim.x; + + shared_tileBuffer[idxInTile] = glm::vec4(0.0f, 0.0f, 0.0f, FLT_MAX); + + __syncthreads(); + for (int i = 0; i < tile.numTriangles; i++) { + int primId = tile.triangleIndices[i]; + Primitive& primitive = dev_primitives[primId]; + glm::vec3 pPix1 = NDCtoPixel(glm::vec3(primitive.v[0].pos), width, height); + glm::vec3 pPix2 = NDCtoPixel(glm::vec3(primitive.v[1].pos), width, height); + glm::vec3 pPix3 = NDCtoPixel(glm::vec3(primitive.v[2].pos), width, height); + glm::vec3 tri[3] = { + pPix1, + pPix2, + pPix3 }; + glm::ivec4 AABB = computeAABB(width, height, primitive); + glm::vec3 bw = calculateBarycentricCoordinate(tri, glm::vec2(pixelX, pixelY)); + if (isBarycentricCoordInBounds(bw) && pixelInTile(pixelX, pixelY, glm::ivec4(tile.tileMin.x, + tile.tileMin.y, + tile.tileMin.x + TILEX, + tile.tileMin.y + TILEY))) { + float depth = (depthRange * -getZAtCoordinate(bw, tri)); + float originalDepth = shared_tileBuffer[idxInTile].w; + shared_tileBuffer[idxInTile].w = fminf(originalDepth, depth); + if (depth < originalDepth) { + glm::vec3 color = primitive.v[0].eyeNor; + shared_tileBuffer[idxInTile].x = color.x; + shared_tileBuffer[idxInTile].y = color.y; + shared_tileBuffer[idxInTile].z = color.z; + } } } + + dev_fragmentBuffer[fragIdx].color = glm::vec3(shared_tileBuffer[idxInTile]); } +#endif __global__ -void shadeLambertian(int numFragments, Fragment* dev_fragmentBuffer, glm::vec3 light) { - int idx = (blockIdx.x * blockDim.x) + threadIdx.x; - if (idx < numFragments) { - printf("%i\n", numFragments); - dev_fragmentBuffer[idx].color = glm::vec3(1.0f, 0.5f, 0.5f); +void shadeLambertian(int width, int height, Fragment* dev_fragmentBuffer, glm::vec3 light) { + int pixelX = blockIdx.x * blockDim.x + threadIdx.x; + int pixelY = blockIdx.y * blockDim.y + threadIdx.y; + if (pixelX * pixelY < width * height) { + int fragIdx = pixelToFragIndex(pixelX, pixelY, width, height); + dev_fragmentBuffer[fragIdx].color = glm::dot(dev_fragmentBuffer[fragIdx].color, light) * glm::vec3(1.0f, 0.5f, 0.5f); } } __global__ @@ -1019,7 +1138,8 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g //TODO: rasterize #if TILERENDER - tileRasterizeTriangles << <1, 128 >> > + dim3 blockDim2dTiles((width + TILEX - 1) / TILEX, (height + TILEY - 1) / TILEY); + tileRasterizeTriangles << > > (curPrimitiveBeginId, width, height, @@ -1027,10 +1147,8 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dev_fragmentBuffer, dev_depth, depthRange, - mutex, - 2, - 2); - tileRasterizeTriangles << <1, 128 >> > + mutex); + /*tileRasterizeTriangles << <1, 128 >> > (curPrimitiveBeginId, width, height, @@ -1040,7 +1158,19 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g depthRange, mutex, 1, - 1); + 1);*/ + +#elif TILERENDERWITHPREPROCESS + cudaMemset(dev_triangleIndicesForFrag, 0, width * height * sizeof(int)); + + computeTrianglesToBeRendered << > > ( + curPrimitiveBeginId, + width, + height, + dev_primitives, + dev_depth, + mutex, + dev_triangleIndicesForFrag); #else rasterizeTriangles << > > @@ -1052,12 +1182,14 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g dev_depth, depthRange, mutex); -#endif - /*shadeLambertian << > > ( - width * height, + + shadeLambertian << > > ( + width, height, dev_fragmentBuffer, light); -*/ + +#endif + //redFragments << > > (width, height, dev_fragmentBuffer); checkCUDAError("rasterization"); From d797c96c3314307cfaecdcfcc9cf9e3c63ddbd4b Mon Sep 17 00:00:00 2001 From: William Ho Date: Wed, 18 Oct 2017 15:51:38 -0400 Subject: [PATCH 09/15] tile based pipeline works for cube... --- src/main.cpp | 4 ++-- src/rasterize.cu | 58 ++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7986959..eff8d62 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,8 +138,8 @@ bool init(const tinygltf::Scene & scene) { return false; } - width = 800; - height = 800; + width = 400; + height = 400; window = glfwCreateWindow(width, height, "CIS 565 Pathtracer", NULL, NULL); if (!window) { glfwTerminate(); diff --git a/src/rasterize.cu b/src/rasterize.cu index 6b1c0ad..117812a 100644 --- a/src/rasterize.cu +++ b/src/rasterize.cu @@ -21,9 +21,9 @@ #define TILERENDER 0 #define TILERENDERWITHPREPROCESS 1 //These need to be defined at compile time, but they need to be mathematically sound. TILEX * TILEY = TILESIZE -#define TILEX 32 -#define TILEY 32 -#define TILESIZE 1024 +#define TILEX 16 +#define TILEY 16 +#define TILESIZE 256 namespace { @@ -1010,7 +1010,8 @@ void tileRasterizeTrianglesAfterPreProcess( int tileGridWidth, Primitive* dev_primitives, Tile* dev_tiles, - Fragment* dev_fragmentBuffer) { + Fragment* dev_fragmentBuffer, + float depthRange) { //Block is tile //Thread is pixel //Loop over the triangles in the bucket @@ -1042,10 +1043,10 @@ void tileRasterizeTrianglesAfterPreProcess( pPix3 }; glm::ivec4 AABB = computeAABB(width, height, primitive); glm::vec3 bw = calculateBarycentricCoordinate(tri, glm::vec2(pixelX, pixelY)); - if (isBarycentricCoordInBounds(bw) && pixelInTile(pixelX, pixelY, glm::ivec4(tile.tileMin.x, - tile.tileMin.y, - tile.tileMin.x + TILEX, - tile.tileMin.y + TILEY))) { + if (isBarycentricCoordInBounds(bw) && pixelInTile(pixelX, pixelY, glm::ivec4(tileX * TILEX, + tileY * TILEY, + (tileX + 1) * TILEX, + (tileY + 1) * TILEY))) { float depth = (depthRange * -getZAtCoordinate(bw, tri)); float originalDepth = shared_tileBuffer[idxInTile].w; shared_tileBuffer[idxInTile].w = fminf(originalDepth, depth); @@ -1060,6 +1061,16 @@ void tileRasterizeTrianglesAfterPreProcess( dev_fragmentBuffer[fragIdx].color = glm::vec3(shared_tileBuffer[idxInTile]); } + +__global__ +void colorTileBorders(int width, int height, Fragment* dev_fragmentBuffer) { + int pixelX = blockIdx.x * blockDim.x + threadIdx.x; + int pixelY = blockIdx.y * blockDim.y + threadIdx.y; + + if ((threadIdx.x == 0 || threadIdx.y == 0) && pixelX < width && pixelY < height) { + dev_fragmentBuffer[pixelToFragIndex(pixelX, pixelY, width, height)].color = glm::vec3(1.0f, 0.0f, 0.0f); + } +} #endif __global__ @@ -1161,17 +1172,40 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g 1);*/ #elif TILERENDERWITHPREPROCESS - cudaMemset(dev_triangleIndicesForFrag, 0, width * height * sizeof(int)); computeTrianglesToBeRendered << > > ( curPrimitiveBeginId, width, height, + (width + TILEX - 1) / TILEX, dev_primitives, - dev_depth, - mutex, - dev_triangleIndicesForFrag); + dev_tiles, + mutex); + + dim3 blockCountForTiles((width + TILEX - 1) / TILEX, (height + TILEY - 1) / TILEY); + dim3 blockSizeForTiles(TILEX, TILEY); + + checkCUDAError("triangle bucketing"); + tileRasterizeTrianglesAfterPreProcess<<>>( + width, + height, + (width + TILEX - 1) / TILEX, + dev_primitives, + dev_tiles, + dev_fragmentBuffer, + depthRange + ); + + shadeLambertian << > > ( + width, height, + dev_fragmentBuffer, + light); + + colorTileBorders << > > ( + width, + height, + dev_fragmentBuffer); #else rasterizeTriangles << > > (curPrimitiveBeginId, From 8357348461ce1a9397ab68a77d429ae8986ccc0b Mon Sep 17 00:00:00 2001 From: William Ho Date: Wed, 18 Oct 2017 16:31:47 -0400 Subject: [PATCH 10/15] added images --- renders/CowLambert.PNG | Bin 0 -> 26608 bytes renders/CowNormals.PNG | Bin 0 -> 38265 bytes renders/CubeLambert.PNG | Bin 0 -> 1945 bytes renders/CubeNormals.PNG | Bin 0 -> 1297 bytes renders/CubeTiled.PNG | Bin 0 -> 127234 bytes renders/FlowerLambert.PNG | Bin 0 -> 4243 bytes renders/FlowerNormals.PNG | Bin 0 -> 4576 bytes src/main.cpp | 4 ++-- 8 files changed, 2 insertions(+), 2 deletions(-) create mode 100755 renders/CowLambert.PNG create mode 100755 renders/CowNormals.PNG create mode 100755 renders/CubeLambert.PNG create mode 100755 renders/CubeNormals.PNG create mode 100755 renders/CubeTiled.PNG create mode 100755 renders/FlowerLambert.PNG create mode 100755 renders/FlowerNormals.PNG diff --git a/renders/CowLambert.PNG b/renders/CowLambert.PNG new file mode 100755 index 0000000000000000000000000000000000000000..b71de74c9da0bcca64c7c135837970f02cf88087 GIT binary patch literal 26608 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 literal 0 HcmV?d00001 diff --git a/renders/CowNormals.PNG b/renders/CowNormals.PNG new file mode 100755 index 0000000000000000000000000000000000000000..51033e11b186d77331a0621db3686dfb3fbee7b2 GIT binary patch literal 38265 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 literal 0 HcmV?d00001 diff --git a/renders/CubeNormals.PNG b/renders/CubeNormals.PNG new file mode 100755 index 0000000000000000000000000000000000000000..6039eb88cf9497c14121196dbe35c4fe5dfeb5af GIT binary patch literal 1297 zcmeAS@N?(olHy`uVBq!ia0vp^r9hm*!3HFE^wl>3DaPU;cPEB*=VV?2IV|apzK#qG z8~eHcB(eheoCO|{#S9F5he4R}c>anMpqgc#E{-7;ac^(lE(#8nIsS1^d%K+GGTm0C zEvY3}wtqM!{_&Faeb$#h=LFvlU|ShBp|8{-kY!o$oUr)`Gnbt7QLB6W@A&^)XYXbI z*S9l1z;!cn|NAZCxp}L9&Hepp_4>OHw(gu2X@12d=J)RpQ{2CP&)$>2yzlzNXa93+ zx3!(zC21Z}{PFp+zUzN~R|=g?H=9@cF@Ns++ZSrm{u-P3+{r6G&3mOceqYSj2bsFh zWT&5g>Z75z=-kJJp37eyoP2A0U!oH+}DH znXke6^u>>zvv>V9l%G{%Rrl@C-5GzUfaHIFT{?T-?;arY@2A_^=C;{^&7U4U%`UHf zCT!<3>+|{Di?63&eP6$)@OobDk#>dW6X zuWb4Es`uD?@n1EbrA5E4_4=y3N#Adhb?Kt*ucvS4r{}5IA8r5Z(A?VaPHOt*&Bf+5 zrIYyeg?T56ZfQUEGroTJLG8yi6_GC%Nfyrf-DG~T;@9Pk(h_GsC*FB5DKdy(%KOUR zzwPh-K3rWRp}r$!+LoqcLZ!=p9oTDba;&|u(L2qt`b61|iO0^}+gDxwflK}vquy** zke1htDn?l%5?kXn zg4S2qw}!a&GCe-t$n;l!&wQr|fr}j$#05z3-t$+S3}l}W>skcV5D{X@^@#cX@`S*r z8Xf$5<~O-cU94aSG*v9Luj$7Dgo1#3H#e_v5;?+t&;Q_}Q-K2V751#%TJDZD5B`ei ztde2<*!Vqo!-^uXf`uu8N5Q5!txO7BifZoBQ|nws7h$(TCpttE#WhX`7j>ap#r0IS zTgwHztpS@>1cB{O+#RwJ&8@9(7r9PFwKhlev{+~diYGTjZh#tr;>Rr&)=SSiom|8f zD8hU1Y;$jJx0Y*S)zRq6Z5ul$x@otomC=7UmuB%+4N)4KXEzTml{dd`?BTNtjqtZm?a{&^}a?5 z_x;8GVM3CoT>Wy}j~vhOOuwQbQO~bDgXVAVVztnr>Ex*fia&0tq!?n;r&~c&8h6<-wikn2jqI{!BZ# ti0%2iLz`TA&)%)-u~%W4 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 EABgPD_y7O^ literal 0 HcmV?d00001 diff --git a/renders/FlowerLambert.PNG b/renders/FlowerLambert.PNG new file mode 100755 index 0000000000000000000000000000000000000000..6b5efbdce2fce8e59ed79ff8bdc441b8d1fb2ebc GIT binary patch literal 4243 zcmZu#XIN8Nw?>MT0Evu&0Vy&{3mA&@oJ4+}jx+b(AN%ZQpR?Dy%DdLy>p6GP)~0+sB0MZCEPUo> z#S)Po1gr(U7zISBfiG93meYa2l2gN>jZfcHfzFZkL zIqB6O;#tr$WOG$JOn2x>(|6nlI z-PHVa#QB}Luiu1`j@!2JMV*O|iMq%DYe`rFyt(M{4sGN$S^B9Y_Pn9UIBq(Q?rdf3 z9Ydi1k0Jd)3j`%q(}?Ij9vTK=S3 zLT$ELr<35#KB0Fg;L;@0O2axusad%?3OKBDL;dXNZ}ccB5#4yI`yqG;5D~l@992L5 zLb$w8TzbNo=mZ1y&QkmDz9z1(H#|2dWs0!U*>T-~X?3f*AZ)t+J~x@aXgoSKH??U` z!k-)SoqNKFu3FZ16}}?8YmFM_&OQexFB`b&OB6fN-sFzHqt4KS+Yy_O0SvdV{`&@C z@>r^&7JJ3~`7L`P*AE;r&;&}z_s@Qd`9fveiq>$3hQEWUU*s0XZ*t8Tt@k0;wf>J1 zO*>)Sl~N>TGiEImF}$SZ0%s5=R)~|k`~$?D$6pubUVq)<)3~9)qA{0*E-cj1xNfaa zTj7rj=CYLm1U2B~DbAQrbmc^0&Qa7p6}&=f-34PNecX}Q+Tb=_Y@EkHx)^S5Vhg>j z|2v{?UOO@3+Sad>y?L8hl#O^4AvtaDq?58W2Kdl(f9GQ0FPAR$7)ARW>^(=KHLUZ; z>nz^^_vJhemI`)1{Wj}P3pBY3ZfxR#nfNs`U_z=Au<~wUTz4-AE+H8(*nOLDpK1_$T+Il%4n!AwkDtV7Jp;$JRxe5@8!g~Jw$ zRETY&@$1^#e(#ap2WwI0iWz6q$OtWy{Z})R*+cVsP*ysmgle>3cq*VT)i-FK6f9zQ zX5GUjMzf(WX=60tgGF~?v}DwU;((I(A086Q?j3!?7Ii%$g|@mH|b`y zN-LdDNkXoqamn&f-W%2yP2oS0-`q#i=ZKWCit6iVI0$cGwDGRD+V&8!i7H;79dS&VYocqg1$?Vir z*mK?rd6CT}uLnb?0@|Q4c~WrlmCSgNVI6L~vQ#SNHKH!tb`X#`SQBmCTx?I2&>aHA z{RzZFfnOg+$`F0~+%RgV+XY5O8ykpCkWFfHtX0YbQ9jJA0i)JMQflWr)?xg4SZ4+V zCIg%|eZX6D)5#XN+ACsjcYTfL5?YE+o@ict5U@6fgl`Cwvr5H|s5*PI-znT#uq+YH zVe9op)1>Y>DlRxRzzKSg6{lZk^iq(E)TRJLxX9skeCNvz@}XVI-X~$mS|3e03=Ji3 zZwD%@*bCafV#pS(D-BNZDWZf_I2U*_Ny>h5JJ|gxIT(3cAixfOU9-oiZddFnN+UYg zF>JX=pY4?Q_lBUyfO$8->c|hohf^Y_PnC7(n&q3+_Jn-{KWY;OAGjrZGe5-4(vH#K6;5SS_7&a&A!?vaA&F=ML~mlDQ# zMIO#4CLRMT$l;CkqJx=uQ0F@nI^^`d7hfX;ZM`Hzb2tp9t>31&lBVX{1o+P;!LqsV z%!uH1u^pBIn|SK<{_V^lF39Uk-q9e6gPF5uk75|6_i|JH9a zK=pDdjVsI-;6Itr6xPYkRWX$9Q|zJ>apULWr63RmnhMJf!W`9HD!GQ}w2%w_qqyF^ z8Z+5A)OidH-3JG}$$*r=t+0q%WkMU@(lsRhC3^Ikf#GuZp|O$7wL=wIaA;*bP10;K zBxF(g-aUw)I2o%z=15DWNLp5$q&@SGUOT<4^+kKQP$=5)`mqvzgaxl7FQZ|qbrM24 zCqah88B;muFCgg<*=``#;pN$9sx-cQV9`rr;OmJqO>;Fj{8-AfB5x5zHrdMao}{DZ zCtkX1Cwj}Xt6B3B*vT2yTwycv1^QN;h0SwXnb4!6$FMU z27`HJ1#t~tgpp5^Jj_YKr6k`P%lUwDpT$9Y@0qdmmx5+VB>zHvJq2?t6>fT`-VL*w ztEsHOqqd|9q(tm=8?2PQRgqsAv#v}zG^?9e>~Z`p+t!I`78~vfoe9iKzFii#qrs89 z)>6yl-sTm$w_~wl+QBuRd2y(Djss32fJjX<#-=YP{rsB^HA1U@FVgv27auKZhJ90P zw~>TCA)6%)OM&-MsMqzZ=&rFFV zva%h*xnaR{b}?p`{}Ahf<@3cf79jB?G{?p@1l@^-n%>c`t6eQW;B$Avrph^Bt7I@z zh`L8izAmsqR! zE($)L48qtyYs+_JqapZ;yE;%_1;WV?KMdeB4)ES_Q_XQ#mSO@6;1SJic$+@U`O4{X zvF5^(rdjtUL#@3kV*jCl?4p!BNi)h*pEa@0Pr@Swr( z)&ydw1C%9o6%!Y6FkfY5Z%2St_RCn$ci`3GGq%th9BOx&x4c`-AN#8*4rbO$?nU*> z%feW0$#F=5{WuVOG1yy+rvn8|l4}H>`h#3#m-!0pB4=htla-_r7x99OmKW|dhC?RK zAyloaTp1yl_QoyKj?09Z;Zjw|E^*+($$&^4r9Lq zI|*g>QY5~(U^pZ1wZ`E5M0irH0dyJLn8YHb>WX}%&3*?7Y)b5VU*zfs{kKZ0OCX!x z2+d-jb9C{;3Hr~S83TJIF9R`05%o4A=A}k#ekiT=nAlnapCKm5y@He_F33RtB# z*Qz6k{_4R+^t8Qj0kU-ki+EkG#5GT~=$NgAJGW#;fwjnKeCH=!c@@LN0dnT*l@ z*&y%vw%qD+F3@y%<)#u{@J|bvx$(D&E-|4TYS#aR>}92y*0Tc(;4>N5x$KY|={!tr ze;NER;w@`LVn2-nD*X?n;3&=RsIyZVfgw-DlC%y`DT8vE@UIVc)y|gS6rgLSBjfV_ z220hZH%T4y1>wNtC{sCD;$qTT%U=QhICftouL4T@7goCGxssIni|CD`eFZ{)5vCcU zuUG4FnVw`@@K1|e8-4lx-tS#n)3004l>Qeu%C$9F{6lRL88(f%$NR&%)QH(y;jg=C z4{dSnacfs7=Mv?LzMKC;nQ8vIn&aV`G-EiNMK?Pbbk)K;#`e$ZNP_tNnZAKb_CEVY z57E^G9^ZfQ)Nj+07xStpp1>@n|8es%09Nv)&uXarcm-#0WVs-~?BI61kj3B+=nagF z6&S6-5#V#Y!Z`7O_S7Z<$1wr$*W<)~tqEVNC)+%au9AMArH{~k(@Va7 zJTc^{?NnjGy@yZdzs2vK(HvRRpKW^39Fo&k9lu*XO^hIm1&i=KlQ2&>MI|=PdJP=1 Zwcoo1QBXl=Fn=Vln4_$XtB_aX{|o%f3p@Y- literal 0 HcmV?d00001 diff --git a/renders/FlowerNormals.PNG b/renders/FlowerNormals.PNG new file mode 100755 index 0000000000000000000000000000000000000000..60da407b74c3648ac3299c450ffdeb803c1664a7 GIT binary patch literal 4576 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 literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp index eff8d62..7986959 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,8 +138,8 @@ bool init(const tinygltf::Scene & scene) { return false; } - width = 400; - height = 400; + width = 800; + height = 800; window = glfwCreateWindow(width, height, "CIS 565 Pathtracer", NULL, NULL); if (!window) { glfwTerminate(); From 3de5bd87688f9c35dad0a7d2d1c16c4bbbd8ac17 Mon Sep 17 00:00:00 2001 From: WilliamKHo Date: Wed, 18 Oct 2017 16:48:45 -0400 Subject: [PATCH 11/15] Update README.md --- README.md | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index cad1abd..3c67a2d 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,36 @@ CUDA Rasterizer =============== -[CLICK ME FOR INSTRUCTION OF THIS PROJECT](./INSTRUCTION.md) - **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 4** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Name: William Ho +* Email: willho@seas.upenn.edu +* Tested on: Windows 7 Professional, i7-6700 @ 3.40 GHz 16.0GB, NVIDIA QuadroK620 (Moore 100C Lab) + +## Overview + +This is an implementation of a GPU rasterization pipeline, designed to simulate the OpenGL graphics pipeline. It implements vertex shading, primitive assembly, rasterization, fragment shading, and a framebuffer. + +## Basic Rasterizer + +//Triangle Image +//Cube Image +//Flower Image +//Cow Image + +These images are the first images I was able to generate from a basic rasterizer. On an implementation level, I am parallelizing over the primitives in my scene, and iterating through fragment coordinates. These renderings made use of axis-aligned bounding boxes for each primitive to cull the fragments that had to be checked. This optimization, is still quite timely. + +The biggest hurdle in this basic rasterizer is the problem of race conditions that arise when trying to write to the same spaces in the depth buffer. Since a mutex must be used to lock access to those parts of the buffer, the rasterization time performance is considerably impacted. + +## Additional Feature -### (TODO: Your README) +### Tile Based Approach +With very limited success, I attempted a tile based approach to rasterizing. This involved a preprocess step of parellelizing over triangles in order to bucket them into their overlapping tiles, and then parallelizing over the tiles. The most significant advantage to this was the ability to use shared memory within tiles to avoid the common global memory accesses to the depth buffer that previously choked my naive rasterizer. With this approach, I was able to speed up the rasterization step considerably for `box.gltf`. With my naive rasterizer on the machine I was working on, I did not succeed in rendering `box.gltf` at higher than `100x100` pixel resolution because the rasterization kernel would exceed the NVIDIA timeout limit. Tiling allowed me to render up to `800x800` pixel resolution. In many ways, this seems promising. -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +However, a truly robust implementation of this approach would have to overcome several hurdles. First off, as evidenced in the above image, there are many visual artifacts that need to be addressed. More important, preprocessing triangles into buckets requires race condition handling, which, for any mesh with significant numbers of triangles, is far worse than my naive rasterizer implementation. As a result, I have not yet rendered successfully any higher polygon meshes, due to the timeout limit as stated above. -### Credits +## Credits * [tinygltfloader](https://github.com/syoyo/tinygltfloader) by [@soyoyo](https://github.com/syoyo) * [glTF Sample Models](https://github.com/KhronosGroup/glTF/blob/master/sampleModels/README.md) From 0ecb68d4888fb9200b00a079375d33adb76572ae Mon Sep 17 00:00:00 2001 From: William Ho Date: Wed, 18 Oct 2017 16:57:20 -0400 Subject: [PATCH 12/15] new Cow Images --- renders/CowLambert2.PNG | Bin 0 -> 8519 bytes renders/CowNormals2.PNG | Bin 0 -> 10697 bytes src/main.cpp | 4 ++-- src/rasterize.cu | 6 +++--- 4 files changed, 5 insertions(+), 5 deletions(-) create mode 100755 renders/CowLambert2.PNG create mode 100755 renders/CowNormals2.PNG diff --git a/renders/CowLambert2.PNG b/renders/CowLambert2.PNG new file mode 100755 index 0000000000000000000000000000000000000000..26b1983038dbf12321b8657202c19bd16941655f GIT binary patch literal 8519 zcmeHt`#+O!9JiS^huJ2F)|weZk=Sau)pBN)6j5}NLx+`QNH#f-ZO-XHrJSay%en4#-5`= zQc_64+5r*z7oL`qBJ}UIwQ^;8{QN3+^WquSuK+uAUSW>5taKg^Vg+ci>y^3+TM41J zM{>4v+97vh2yB8D&r{a+G^24NP8On~BPS1#Wb(F~Ne!IV@zvQXcyrl5(CyN*!1=G8 zZnKjS6BFkmE1pyzcz<%a^ppSL+2gOOCoTsse|~kLr}*55)x&4c60h6rC|_?z*|9b4 z|G)Xay9t}}mKKEP!o&(YieDcw^fx&((j|YOH$Vva7~IogNnQD#8d^KMjTK(5N8o;n zDQg{bq~t|VuNy^_JvLumd|W-k^_iN;kfDFx+njs%VbYycNcl+$yB3WBAH7a&}jpOL)-eo5xGPLN<+G-DVv~-)Jk{o#Rb0U;dFr zmFC$c$=1PI*cp*?O{z__KWRaQGj7Ilnt(uuOP_w(WR&V}R(>akp2O zp0z!O( zrnO|*9WpYPsvc}e^N)D1d8hQ{#WK6YCxbr{?1$kN-ycmSzDl8PcOLY4)0(>uK`W?8 z*;+Db*rMISo{0+k;jo)bh+N?AC{>Gb)bXf8@|Nb^O|$zVSMTikH_c_^uNG}U%J6Fk zPt|=s8^2`kvJbh<{alCflU)O|#rD2bS6p8q9gY99XpjoeU{}sr^JLQ}ET-~SPCg7@ z`RRWUW*sp9bvKaeDf%;JF;(^ZtxEEE?cb64%tL|na2|||TU}V0-|WLBrdt0gOPys1 z%W&z7<8D_A5L|gh()QTZpik|OBMW9Jz8H7DRFN}PVv7<;+E2|5N$R1ygMoLti2^TI zd7-7HD?w#e3J&9|APu;5bdTW-({rxv;04wmbBWyIl*hj5H}C_gy%B#$M(&3cKRy_K zp`|n86Prn$bv43G4{OXfHf*M;BNM%;GxU3PLVQ~X7r)b0URtC7CYDm#pSr;}ShGN_Q=fX0#7>cn8<}ubfS2`IUJ|->B2L zf%&KVj^l`Hl|7|6{*CSx3 z(@bm2LOT*q_18b+!LFc6BBtiAYv9BGNd3zitm-h?(an>9*%M3=xz!z$mt3<^b_HB1 zg_zWpUAh|;RZ}$7gZ|sUC6J{Z$Rf>51igg^!u;P}80u2HMwJ)vvTx)mg#_{yjP|46 ztzI2qwbf-`y%h6T)10?DrdL2otItG7H&`0To+?oPR(izE;yX*@@O=OKpw;Ew^8?4j zy|cq%|0TflqPG+>3D{Vs=RF#xs*>|0i zv{GAC-4JhjfT6oc5oAe>>ilj&S4!9dgL)gxeF-*PZ#&{>k+@j#tla%o|Yd7 zs(Tpn{8NMIuKjUfWn}l%kLDP$R8jaebwq5rAI~~1nxgM(&8^^d_x^rh8 zrInnOuPvVSKVJRXiALE%=QlP={Ac^*BZQR)(a9w}n&R*`FB{!n4rcWSeQ@qLj+kNO zGsDJqz24)wxfGL%Q49nS7=*X&jHgr{`4F&B+-?m-d0}`qYKm3+4>a>ps4V(hh}h#r znmmoyGME)$e;v;DCh>OE2W{lR!ssQhA0J2UGFS&K9tauygA?8lmL)8|`fEX~HI+jn z8-ui>2KQFKM#yASlDnm1hl^j048GIa%>ATgPvqD%v&Hm8cw+;T(v2bF|CB2qycu*& zoTJ&@D*l?i9qH#vvHW@TIG5eP6Z62MmEX{Dqw-G7-=|Am+{fe3+V9e(Vw9$i|GVcG zvD^b6igy3kch~o!OOc=G+&>{}tRwc9mI0Xp_~J$Mla4M;oVz+zRe(->Q9$Hu7|qO-+#0 zho`S32Lq*xhoTh?en_M>bX#6a!qXS~fG?Bl zj0&@kW^3U(jzbm>0Vzfa%xP8Z#jGaAhoSlG^?`Kt=W%Rb3>h26a6$+LPmUPpwpKKY zr#h)SoHO)z_9`0py8XE7x@;>Ju%{V%nnK_ZTZpmHZl2hZRH%Zm26*#F`(zAahYv41 zs@g(*vG;c1fYq)5p-%7o@KqYld3o_s!Ccm6E6U1bXK1%4Pzt>x@N&i@VSERG(R5jc zIKCdqi4r?O;v1r=ue3NFp3qslowak5@384 zOgO6XGBk1jvO2HMmA}8M*$7z;ocp{(?2ZK?WP%4$td%|vNM(#jw!{%RgjCq0Glxle zFX*=XSg0$ovh?Turt|4jt}w4;A_qDh84R;&6F^v$YEhrO05VZhexT7988)~hD$IgX5%75-_qK|Y0=cjm?@H8ZBE0x|`!DV-0(GfrRSO(~09tW!n&VSH#NuC_x*yHl-|0{uNVg$j8GRETJx<~%OfnVM%#8Jn zO0YvpT8F1M%(9CW4OibKsBTwN`m_@y(&Au*A}tBt81V71qqZxsrA5p167|zlQw#Hd z9MgP;XxOOoVFQDNzuW~%9bwP>wus1%;+cMzbojMz%AH9<8h*g) zQ-{1_F@`6+MyCe6+q>S-CCv%5p^2}8zVz&}oU9;sh`be-M6nW7OnkcJ)>5E!87^GeZlzc)n+jLSZ;B_$7&a^uQ?ncZ;T@|ts|9DsLemCqPa^8YJLX+pMGD%irF1swQ#CPPLn70Z^0 z;HGvwcw~rdGZ93fY5SUQ+i{q?-IkUjiBLKCl^Vh9d5SmSN<}MrQf3$Kztg{*zQF}g!l37osWA;g;!CBA){I)s2YOPHgH9ygolXk=J2J>RG7#FeA7!l{Um zsY-k>FfBn!{u((2(utr*AmRGCMEe-ccnq>8{9YziiJudr1Y$2zP`Kr06I>=6ailv3 zlUf)L?*IGJPifrY>ZR2=ecT5%l+*lhrAfrV@4Jp0WT2;@SpW^F#0^PiW_vVvw7&C2 zNa4#P#rtU+uMQT!LWWJP+mG~xX6qOc@dl1%Zdo^P3`|GYtM3_-M#^bscuEk{5F zB*tvUL&Z4}l+u<5jcaQsw)g~dh}NVJxCYUpa!8+^9UGnO)|06RT=c4W3pUFpF=Is) zM}EIMnFK$lkE=0k1hqXvs6C|gXwP%!GAUI=f*50rcQm+EnH!Rjs`)+gGQ_mUn}R{o z72u;{_xlnIs9ft%!Jol;bQt(V?2jkoVwp`P8$lVz15;pzadQZwk3L!GK4Kpa8xdo4 z@QxI3UWjtV5BH&Q)c5{BACiWvUD7O6e3q*ddA=IA#{3n*JbS~^!vaULc zsDM0r8lY|?yy7CG6$E65GMDl(F+<%*#lH2vqXP+uGigoJ^zk4MUz7^0AS zLTI<>9OWGTSXlrc@nTJFfeolkg!1@?Y1QG8l)zZ3Vtd0g0WrC zC-+n&2Y4%i5Yxjz4?K8eo#s-;n*j>X?6LD&Je#RAz8$-UG%g%|3!~n-JzYm1Cu@aI zRyXM@lxxX6wK5ZJoNV`EwaHo&9Idg;;dOQR>18RWjLr?JiigClgYS0h|I2i6CG?G0 zc~UrW#E2XhM61R9;3MUaVo`6HJqKZ@`<1PRSlpe!m2!A|}y{rlx z_Bt_h=WwnyIVr)$utn>QkJ2LmZDB}G0A;8h)SH6eEsoU%Igx~8&eOxRRJ=)R4VFD# zF^fjATwZyq3=V`wGOGo-jii)A23j)KUy(c|=iFAYO{N|P(w7GTrLE^Q)f$on6ZJkM zH4h)=f5S$H*6VVKUXvdHh$<#^`>&{v^hkSY{c0lI~BfU zA%3&T*y_*zAZ2j*Jn*awX14mO$(_UE3fZnfJR#$NBlDua@XY59vH(1|hstU~c zD_?ZBzUbSx@?QWo@@IvzY6?}&ISEF%$7X9^AYdCPNdYg_u_D{lw zS|@v`_ZDy9tw$~O{&p3#Ai5&?1>LhJ(%$E#qv@@eJuSLihnAq8V^8|LmuJtdV}A(d zbz@$%KzOj}+o+R;q??6%Y*{4wJh}WA_Paq z@teRrS`#xOm%mekv0AA#d~$K8^Jj1~$2CD9_z>uaQ4CwZsS(t)XaRX2wNNFA*)P{x z@+8J;@!9Yj0oedwiL?W?y z>~)*1Yho*D6UZ-x_KI-S1?5Mh_756K`;+VtEy-~8n<6ts4!dEEN$R_AoBq${1ZZ|H zvrldr-);cPiTrepTk--@Z;dAf4brsv#sqqQ$Yj}G1iW(xt{zHy^5qyfDeg(TZm#l(-cty=F0X=3UBmOajFc0-h>(lzub; zBSSqWLtfv+6t(T=uqB?_(|?=wOldQ8kw|zjD=e@)U2lFxmOU4~jXo~qT8kCzC@OEaj z{A?nGZ-o$oUR-8gLrArj+CtF@Pmn-V5%!>d7M%$%BwWGf0!3|s_tv%6IKuu}C5ze)fakj6r%(POa40y1u~ z^F{m(8)c7|ZMtnQCoB#*wc94anwcQW1vw4}zK78uKBVGb*%7OS{PnTKgr0V24BZKo zCM`WE^U6K}ZQ(8oG`{VjU~0ftu6+ztYLv7s&->TD?b%7Pp*$3`l1->#1h8dNVn@%f z8=X?LreNf$cS45_>@xNS(1;qrr|#QsfpiE6UYFxxUdPo7{+%XY9CUN@IC|La1j@B_m$CMBT%&lC|ctD$qBXJ!o1 zDJsc2DoFf2fCKGbXn%je!JBFst^ryl=-{f3t!B>=jnVX^@)uT6ap9vg59Rn)ycS0; z?p-XqT~UFvZBHP*LGRV%yW8zpI7L#qe1*06+>3(j51r7(k+J+j!|KAZyS^i%Ytu^0 z3|BADWd;GCnBR7|0HrT|X1i?Sk6pS8`a$$-$FAJg#ToyvIn8T+-Uqv;Z!?jv9$=6Q zy_tjeniwbM9-6kD`4R?tijPr0E8J$X$Fhww%2;5pl)<&x_FlCyIvn$OqQw7SHY&{0 zaF-uCWi|M%nI5*+b)ycB2V!-8dimO~xeNmKhAceK%8?qe5>z!wG4uXA0A~_$k*OK{ ziHaA+zL#X}shv?xXC}{Ua@9!qI_A{X(NX?w)zqE1k6%OD&lId{gz&(2HK&d>`1dZO zu-$K}Gogv97FlT$ zr*_0J!(dAw-Iz1nzV!$Njjov7!kivB+EA$YuPD(dz2{UC7@PT^&RhTIm*GsQ;ojR+ zM_+u;DWi;^3F3NI7-Ku%>UML~NYAOF$`|AVz#+<#cp>%o+Z*k@HP>w>1X$s?EbSjQ zm%w2U1tuP@y))A*x4Ct6-`iflm-(Th=(R^pquc*?`uy8HSNQnvuL;&a$>VB?ebr>; zLp_x$b<+U=T+g(*M(prrp~e)$01-&HN@)4$Hc!H(?$*ty_>4go{;CO@WaQI9Q50|L zmUU(A!AgrIP>2-f_w0PA8WYVn@zF>M5J>L4e7(&JK>JtR>)2%W+}swbQ46m9(ldj! z;q(8kL(cqrdTXl-RaYrR7wQFFyUW-9S)L&i_gpZ_UO@+}sIzwj|^?*1> z7j&|2{yOstbT4MfYJVDb&rildY189e{?gpb(O}TUuJ5=NAc*!nQ8ho1B5WjZ)d*^! zH<82@bSH>;odK@%gGL$Z9C41yTJr6lZyoikxNf6>LGF8%bt-~&c=xR9LG_eQJR_>6 zc(;$YIEsq@==XAVuIBZqT2;+c6R@-K9Hoox zKe-ye`SG~W;tKU!2JRR~QqST*(MSf+rx?pWxUJ`-B}nK^lG{cg{}AvOE|qIy5f?)v zWF>_MKR!Fz3YOAzcj}vo-ln$P&$i3c-}yJHIK$zue%(Ew;Zbqn2vGan+m2Y5<#-Qw z*U?ouM-;%#NnzgErXNT@dqj(S_N>nT!*8}_IWI@z Zs^d3$+8xUP|NJ1e*Y1FAv9)K+{{YZtPD}s* literal 0 HcmV?d00001 diff --git a/renders/CowNormals2.PNG b/renders/CowNormals2.PNG new file mode 100755 index 0000000000000000000000000000000000000000..312bc8173d41ae870a848a330eb3034633551759 GIT binary patch literal 10697 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 #define TILERENDER 0 -#define TILERENDERWITHPREPROCESS 1 +#define TILERENDERWITHPREPROCESS 0 //These need to be defined at compile time, but they need to be mathematically sound. TILEX * TILEY = TILESIZE #define TILEX 16 #define TILEY 16 @@ -1217,10 +1217,10 @@ void rasterize(uchar4 *pbo, const glm::mat4 & MVP, const glm::mat4 & MV, const g depthRange, mutex); - shadeLambertian << > > ( + /*shadeLambertian << > > ( width, height, dev_fragmentBuffer, - light); + light);*/ #endif From 59e566fb0081f0508044d96cfd5e4054d68e4a06 Mon Sep 17 00:00:00 2001 From: WilliamKHo Date: Wed, 18 Oct 2017 16:59:18 -0400 Subject: [PATCH 13/15] Update README.md --- README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c67a2d..f99b155 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,12 @@ This is an implementation of a GPU rasterization pipeline, designed to simulate ## Basic Rasterizer -//Triangle Image -//Cube Image -//Flower Image -//Cow Image +| box.gltf | flower.gltf | cow.gltf | +|:-----:|:-----:|:-----:| +|![](renders/CubeNormals.PNG)|![](renders/FlowerNormals.PNG)|![](renders/CowNormals2.PNG)| +|![](renders/CubeLambert.PNG)|![](renders/FlowerLambert.PNG)|![](renders/CowLambert2.PNG)| + + These images are the first images I was able to generate from a basic rasterizer. On an implementation level, I am parallelizing over the primitives in my scene, and iterating through fragment coordinates. These renderings made use of axis-aligned bounding boxes for each primitive to cull the fragments that had to be checked. This optimization, is still quite timely. @@ -25,6 +27,9 @@ The biggest hurdle in this basic rasterizer is the problem of race conditions th ## Additional Feature ### Tile Based Approach + +![](renders/CubeTiled.PNG) + With very limited success, I attempted a tile based approach to rasterizing. This involved a preprocess step of parellelizing over triangles in order to bucket them into their overlapping tiles, and then parallelizing over the tiles. The most significant advantage to this was the ability to use shared memory within tiles to avoid the common global memory accesses to the depth buffer that previously choked my naive rasterizer. With this approach, I was able to speed up the rasterization step considerably for `box.gltf`. With my naive rasterizer on the machine I was working on, I did not succeed in rendering `box.gltf` at higher than `100x100` pixel resolution because the rasterization kernel would exceed the NVIDIA timeout limit. Tiling allowed me to render up to `800x800` pixel resolution. In many ways, this seems promising. However, a truly robust implementation of this approach would have to overcome several hurdles. First off, as evidenced in the above image, there are many visual artifacts that need to be addressed. More important, preprocessing triangles into buckets requires race condition handling, which, for any mesh with significant numbers of triangles, is far worse than my naive rasterizer implementation. As a result, I have not yet rendered successfully any higher polygon meshes, due to the timeout limit as stated above. From 7be0454a072c605ee4538e597794c08a1927fbb2 Mon Sep 17 00:00:00 2001 From: WilliamKHo Date: Tue, 24 Oct 2017 09:58:52 -0400 Subject: [PATCH 14/15] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f99b155..aca1f7f 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ CUDA Rasterizer ## Overview -This is an implementation of a GPU rasterization pipeline, designed to simulate the OpenGL graphics pipeline. It implements vertex shading, primitive assembly, rasterization, fragment shading, and a framebuffer. +This is an implementation of a GPU rasterization pipeline, designed to simulate the OpenGL graphics pipeline. It implements vertex shading,rasterization, fragment shading, and a framebuffer. ## Basic Rasterizer From e84ad1c3f0bcc168ac95f9d25acddf767601b11c Mon Sep 17 00:00:00 2001 From: WilliamKHo Date: Tue, 24 Oct 2017 10:00:20 -0400 Subject: [PATCH 15/15] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aca1f7f..9affe29 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ This is an implementation of a GPU rasterization pipeline, designed to simulate ## Basic Rasterizer -| box.gltf | flower.gltf | cow.gltf | -|:-----:|:-----:|:-----:| -|![](renders/CubeNormals.PNG)|![](renders/FlowerNormals.PNG)|![](renders/CowNormals2.PNG)| -|![](renders/CubeLambert.PNG)|![](renders/FlowerLambert.PNG)|![](renders/CowLambert2.PNG)| +|| box.gltf | flower.gltf | cow.gltf | +|:----:|:-----:|:-----:|:-----:| +|Fragment Normals|![](renders/CubeNormals.PNG)|![](renders/FlowerNormals.PNG)|![](renders/CowNormals2.PNG)| +|Lambertian Shading|![](renders/CubeLambert.PNG)|![](renders/FlowerLambert.PNG)|![](renders/CowLambert2.PNG)|