I have implemented a depth buffer using a std::vector with size 640 * 480. I can write and read from the buffer fine, but I have noticed the buffer appears to be copied along the left and right edges. The buffer is written row by row, going left to right and then going down one row.
I am quite certain the issue is related to the depth buffer, as disabling read from the buffer fixes the artifacts and shows the buffer is still being written properly.
I am using SDL as the graphics library, but not OpenGL.
This buffer should only show one trapezium down the middle. The extra bits on the left and right should not appear.
What is happening to cause these artifacts? Alternatively, could I know some methods to debug this better.
Minimum code to replicate (as far as I can tell):
#include "SDL.h"
#include <vector>
#include <algorithm>
#include <iostream>
struct vec3d {
float x = 0;
float y = 0;
float z = 0;
};
struct tri3d {
vec3d p1;
vec3d p2;
vec3d p3;
};
struct vector2d {
float x;
float y;
};
float vect_dot_vect(vector2d a, vector2d b) {
return(a.x * b.x + a.y * b.y);
}
int draw_tri(SDL_Renderer* renderer, std::vector<float>& buffer_out, tri3d triangle, int half_x_width, int half_y_width, int depth_test) { // depthmap is a linear array. Buffer out is pointing to the first value
tri3d scaled_tri = triangle;
// Find bounding box of tri
int x = (int)std::min(std::min(floor(scaled_tri.p1.x), floor(scaled_tri.p2.x)), floor(scaled_tri.p3.x));
int y = (int)std::min(std::min(floor(scaled_tri.p1.y), floor(scaled_tri.p2.y)), floor(scaled_tri.p3.y));
int wx = (int)std::max(std::max(ceil(scaled_tri.p1.x), ceil(scaled_tri.p2.x)), ceil(scaled_tri.p3.x)) - x;
int wy = (int)std::max(std::max(ceil(scaled_tri.p1.y), ceil(scaled_tri.p2.y)), ceil(scaled_tri.p3.y)) - y;
// Find edge vectors
vector2d ac;
ac.x = scaled_tri.p3.x - scaled_tri.p1.x;
ac.y = scaled_tri.p3.y - scaled_tri.p1.y;
vector2d ab;
ab.x = scaled_tri.p2.x - scaled_tri.p1.x;
ab.y = scaled_tri.p2.y - scaled_tri.p1.y;
float cc = vect_dot_vect(ac, ac);
float cb = vect_dot_vect(ac, ab);
float cp;
float bb = vect_dot_vect(ab, ab);
float bp;
float invDenom = 1 / (cc * bb - pow(cb, 2));
float u;
float v;
float w;
float x_dif = x - scaled_tri.p1.x;
float y_dif = y - scaled_tri.p1.y;
int full_y_width = half_y_width * 2;
float twoarea = (ab.x * ac.y - ab.y * ac.x);
float barycentric_depth_weights[3] = { scaled_tri.p1.z, scaled_tri.p2.z, scaled_tri.p3.z };
float depth_map_value;
for (size_t i = wy; i != 0; i--) {
for (size_t q = wx; q != 0; q--) {
vector2d ap;
ap.x = q + x_dif;
ap.y = i + y_dif;
cp = vect_dot_vect(ac, ap);
bp = vect_dot_vect(ab, ap);
// Find barycentric coords
u = (bb * cp - cb * bp) * invDenom;
v = (cc * bp - cb * cp) * invDenom;
w = abs(1 - u - v);
depth_map_value = (w * barycentric_depth_weights[0] + v * barycentric_depth_weights[1] + u * barycentric_depth_weights[2]);
// Test if in tri
if (u >= 0 && v >= 0 && u + v < 1) {
// Test depth buffer
if (buffer_out[(y + i) * full_y_width + x + q] < (0.0625 + depth_map_value)) {
buffer_out[(y + i) * full_y_width + x + q] = depth_map_value;
}
}
}
}
return 0;
}
SDL_Window* win_make_window(int display_width, int display_height, SDL_WindowFlags flags) {
// Returns an SDL window given screen size and flags
SDL_Window* window = NULL;
window = SDL_CreateWindow("Minimum code", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, display_width, display_height, flags);
if (window == NULL) {
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
}
return window;
}
int draw_buffer(SDL_Renderer* renderer, std::vector<float>& buffer, int half_screen_x, int half_screen_y) {
// Iterate over every pixel and draw
int depth_map_value;
int screen_y = 2 * half_screen_y;
for (size_t i = 0; i < screen_y; i++) {
for (size_t q = 0; q < half_screen_x * 2; q++) {
depth_map_value = buffer.at(screen_y * i + q) * 100;
SDL_SetRenderDrawColor(renderer, depth_map_value, depth_map_value, depth_map_value, 255);
SDL_RenderDrawPoint(renderer, (int)q, (int)i);
}
}
return 0;
}
int main(int argc, char* argv[]) {
const int half_screen_size[2] = { 320, 240 }; // Half size of screen. Needed it elsewhere
const SDL_WindowFlags flags = SDL_WINDOW_SHOWN;
// SDL startup boilerplate
SDL_Window* window = NULL;
SDL_Surface* screenSurface = NULL;
SDL_Renderer* renderer = NULL;
// The tris, already projected
tri3d tri1;
tri1.p1 = { 577.173828, 453.201538, 1.37657264 };
tri1.p2 = { 108.381744, 399.609772, 1.03054810 };
tri1.p3 = { 547.989380,70.1635742,1.20407486 };
tri3d tri2;
tri2.p1 = { 108.381744, 399.609772, 1.03054810 };
tri2.p2 = { 131.230850, 108.719635, 0.930727124 };
tri2.p3 = { 547.989380, 70.1635742, 1.20407486 };
//Create depth buffer
std::vector<float> depth_buffer = {0};
depth_buffer.resize(4 * static_cast<__int64>(half_screen_size[0]) * static_cast<__int64>(half_screen_size[1]));
// Catch startup errors
if (SDL_Init(SDL_INIT_EVERYTHING) < 0) printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError()); // Catch startup errors
else {
SDL_Event event_handle;
window = win_make_window(half_screen_size[0] * 2, half_screen_size[1] * 2, flags);
screenSurface = SDL_GetWindowSurface(window);
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
// Draw tris to screen. No pixels actually drawn for debug purposes, only modifies depth buffer
draw_tri(renderer, depth_buffer, tri1, half_screen_size[0], half_screen_size[1], 1);
draw_tri(renderer, depth_buffer, tri2, half_screen_size[0], half_screen_size[1], 1);
// Draw the buffer to screen
draw_buffer(renderer, depth_buffer, half_screen_size[0], half_screen_size[1]);
SDL_RenderPresent(renderer);
}
// Close everything else
std::cin.get();
SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
SDL_Quit();
return 0;
}
This is a school project and as such, I am not allowed to use SDL provided features except to draw to screen and for handling windows.
I modified the code to draw the depth buffer as it was being calculated, and noticed that when drawing from left to right, columnwise, the leftmost artifact no longer renderers. By changing the range of the region rendered, it appears that writing to one spot on the depth buffer also writes to another. No idea what to make of this yet.
No idea what's the deal with "half" sizes as you seem to use full size everywhere, but your array indexing is wrong. When iterating rectangle with [width, height], correct indexing code is e.g.:
for(int y = 0; y != height; ++y) {
for(int x = 0; x != width; ++x) {
int pixel = pixel_array[y*width+x]; // not y*height!
}
}
Correct that in both places you index your depth array:
in draw_tri, buffer_out[(y + i) * full_y_width + x + q] - should be full_x_width, which you don't have yet,
in draw_buffer, depth_map_value = buffer.at(screen_y * i + q) * 100; - should be screen_x.
Related
It also draws multiply triangles, they are all displaced and in wrong scale.
I am trying to make my own implementation of triangle rasterizer found at:
https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation/rasterization-stage
I have no idea what is wrong with my code.
#include<fstream>
#include<cmath>
class Vertice
{
public:
float x, y;
Vertice(float x, float y)
{
this->x = x;
this->y = y;
}
void fitToImage(int imageWidth, int imageHeight)
{
x = (x * (imageWidth / 2)) + (imageWidth / 2);
y = (-y * (imageHeight / 2)) + (imageHeight / 2);
}
};
class Image
{
public:
int imageWidth, imageHeight;
unsigned char* pixels;
Image(int imageWidth, int imageHeight)
{
this->imageWidth = imageWidth;
this->imageHeight = imageHeight;
pixels = new unsigned char[imageWidth * imageHeight * 3];
}
~Image()
{
delete[] pixels;
}
void setPixel(int x, int y, int red, int green, int blue)
{
int help_var = ((y * imageHeight) + x) * 3;
pixels[help_var + 0] = (char)red;
pixels[help_var + 1] = (char)green;
pixels[help_var + 2] = (char)blue;
}
void fillPixels(int red, int green, int blue)
{
int help_var = imageWidth * imageHeight * 3;
for (int i = 0; i < help_var; i += 3) {
pixels[i + 0] = (char)red;
pixels[i + 1] = (char)green;
pixels[i + 2] = (char)blue;
}
}
//-------------------BARYCENTRIC TRIANGLE RASTERISATION------------------------
float edgeFunction(const Vertice& A, const Vertice& B, const Vertice& P)
{
return ((P.x - A.x)*(B.y - A.y) + (P.y - A.y)*(B.x - A.x));
}
void fillTriangleBarycentric(const Vertice& v0, const Vertice& v1, const Vertice& v2)
{
Vertice p(0.0f, 0.0f);
for (int x = 0; x < imageWidth; x++) {
for (int y = 0; y < imageHeight; y++) {
p.x = x + 0.5f; p.y = y + 0.5f;
float w0 = edgeFunction(v1, v2, p);
float w1 = edgeFunction(v2, v0, p);
float w2 = edgeFunction(v0, v1, p);
if (w0 >= 0 && w1 >= 0 && w2 >= 0) {
setPixel(x, y, 0, 0, 255);
}
}
}
}
//-----------------------------------------------------------------------------
};
int main()
{
Image image(800, 600);
image.fillPixels(255, 255, 255);
Vertice a(0.2f, 0.5f);
Vertice b(-0.5f, 0.0f);
Vertice c(0.5f, -0.5f);
a.fitToImage(image.imageWidth, image.imageHeight);
b.fitToImage(image.imageWidth, image.imageHeight);
c.fitToImage(image.imageWidth, image.imageHeight);
image.fillTriangleBarycentric(a, b, c);
std::ofstream imageFile;
imageFile.open("./drawing_triangle_test_image.ppm");
imageFile << "P6\n" << image.imageWidth << " " << image.imageHeight << "\n255\n";
imageFile.write((char*)image.pixels, image.imageWidth * image.imageHeight * 3);
imageFile.close();
return 0;
}
Here is the image I get after running my program.
Thanks for any help!
Here is the better result (where setPixel is using imageWidth instead of imageHeight):
y * imageHeight
Is definitely the type of error your code has (might have multiple instances). You need to multiply the y position by the width. Otherwise, you'll end up interlacing the triangle at random x positions.
The fact that you get four triangles relates to 800/600 simplifying to 4/3. Had you rendered to 797 by 603, you'd probably would have gotten some random mess of horizontal lines.
In addition to #Jeffrey's correction your edge function is also not quite right. It should be
float edgeFunction(const Vertice& A, const Vertice& B, const Vertice& P)
{
return ((P.x - A.x)*(B.y - A.y) - (P.y - A.y)*(B.x - A.x));
}
i.e. there should be a negative sign between the two terms (because it is the cross product of the two position vectors AB and AP).
I have to write a function, which detects intersection and returns true or false.
I have Shape.cpp file, and rectangle.cpp, circle.cpp files inherits of it. I tried to calculate it, but i failed. There is no error, but when my program starts, it crashes. MY Question is why it crashes? is my way wrongs? Here is circle.cpp file.
bool Circ::intersects(Shape* pshape)
{
Rect *p1 = dynamic_cast<Rect*>(pshape);
Circ *p2 = dynamic_cast<Circ*>(pshape);
if(p1)
{
float circleDistance_x = abs(p2->getPos().x - p1->getPos().x);
float circleDistance_y = abs(p2->getPos().y - p1->getPos().y);
if(circleDistance_x > (p1->getSize().x/2 + p2->getRad()))
return false;
if(circleDistance_y > (p1->getSize().y/2 + p2->getRad()))
return false;
if(circleDistance_x <= (p1->getSize().x/2))
return true;
if(circleDistance_y <= (p1->getSize().y/2))
return true;
float cornerDistance_sq = (circleDistance_x - (p1->getSize().x/2)) + (circleDistance_y - (p1->getSize().y/2))*(circleDistance_y - (p1->getSize().y/2));
return (cornerDistance_sq <= p2->getRad()^2);
}
return false;
}
This is not the code all i want to write. But when it fails, i stopped to write.
and my Shapes.h file
#ifndef _SHAPES_H
#define _SHAPES_H
struct Point2d
{
float x, y;
};
struct Point3d
{
float r, g, b;
};
class Shape
{
protected:
bool m_bMarked;
Point3d m_col;
Point2d m_veldir;
Point2d m_pos;
float m_vel;
public:
Shape(Point2d& pos, Point2d& veldir, float vel, Point3d& col)
:m_pos(pos),m_veldir(veldir),m_vel(vel),m_col(col)
{
m_bMarked = false;
}
virtual ~Shape() {}
virtual void draw() = 0;
virtual bool intersects(Shape*) = 0;
inline void move() { m_pos.x += m_veldir.x*m_vel; m_pos.y += m_veldir.y*m_vel; }
inline void invert_xdir() { m_veldir.x *= -1; }
inline void invert_ydir() { m_veldir.y *= -1; }
inline void MarkShape() { m_bMarked = true; }
inline void UnMarkShape() { m_bMarked = false; }
inline bool isMarked() { return m_bMarked; }
inline void increase_vel() { m_vel += 0.01f; }
inline void decrease_vel() { m_vel -= 0.01f; }
};
#endif
And finally my ShapesMain.cpp file
#include <time.h>
#include <GL/glut.h>
#include <cmath>
#include "Rectangle.h"
#include "Circle.h"
// YOU CAN CHANGE THE NUMBER OF SHAPES
#define SHAPE_COUNT 20
// YOU CAN MODIFY WINDOW SIZE BY CHANGING THESE
// YOU MAY ALSO VIEW WINDOW IN FULL SCREEN
#define WINDOWX 500
#define WINDOWY 500
// UNCOMMENT THE LINE BELOW TO STOP MOVING SHAPES
//#define NO_MOTION
// CHANGE THESE DIMENSIONS HOWEVER YOU LIKE
#define MAX_SHAPE_DIM 70
#define MIN_SHAPE_DIM 10
float g_windowWidth = WINDOWX;
float g_windowHeight = WINDOWY;
Shape* g_shapeList[SHAPE_COUNT];
int g_numShapes = 0;
bool g_bShowIntersection = true;
//------------------------------------
void Initialize()
{
srand ( time(NULL) );
// delete previous shapes, if there is any
if (g_numShapes > 0)
{
for (int i = 0; i < g_numShapes; i++)
delete g_shapeList[i];
}
// create a new shape repository
do {
g_numShapes = rand() % SHAPE_COUNT; // number of shapes are randomly determined
} while (g_numShapes < 5); // we dont want to have less than 5 shapes
int rect_count = g_numShapes * (rand() % 10 / 10.0f);
int circle_count = g_numShapes - rect_count;
int half_wind_x = 3* g_windowWidth / 4;
int half_wind_y = 3* g_windowHeight / 4;
int max_dim = MAX_SHAPE_DIM; // max dim. of any shape
int min_dim = MIN_SHAPE_DIM; // min dim. of any shape
int quad_wind = g_windowWidth / 4;
for (int i= 0; i<g_numShapes; i++)
{
float x, y;
float v1, v2;
// set positions
do {
x = rand() % half_wind_x;
} while (x <= quad_wind);
do {
y = rand() % half_wind_y;
} while (y <= quad_wind);
Point2d pos = { x,y };
// set velocity directions
do{
v1 = rand() % 10 / 10.0f;
v2 = rand() % 10 / 10.0f;
} while (v1 == 0 || v2 == 0);
v1 *= (rand() % 2) ? -1 : 1;
v2 *= (rand() % 2) ? -1 : 1;
float vnorm = sqrt(v1*v1 + v2*v2);
Point2d veldir = { v1 / vnorm, v2 / vnorm };
// set velocity
float vel;
do {
vel = rand() % 2 / 10.0f;
} while (vel == 0);
#ifdef NO_MOTION
vel = 0.0f;
#endif
//set color
float R = rand()%100/100.0f;
float G = rand()%100/100.0f;
float B = rand()%100/100.0f;
Point3d color = { R,G,B };
// construct objects
if (i < rect_count)
{
float wx;
float wy;
do {
wx = rand() % quad_wind;
} while (wx < min_dim || wx>max_dim);
do {
wy = rand() % quad_wind;
} while (wy < min_dim || wy>max_dim);
Point2d size = { wx, wy };
Rect* pRect = new Rect(pos, size, veldir, vel, color);
g_shapeList[i] = pRect;
}
else
{
float rad;
do {
rad = rand() % quad_wind;
} while (rad < min_dim || rad>max_dim);
Circ* pCirc = new Circ(pos, rad, veldir, vel, color);
g_shapeList[i] = pCirc;
}
}
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
}
//-------------------------------------
// This function handles the intersections of shapes.
// if the user is not interested in marking intersections
// s/he can set bMarkIntersections to false..in this case
// no intersection test is performed
void MarkObjects(bool bMarkIntersections)
{
if (bMarkIntersections == false)
{
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->UnMarkShape();
}
else
{
// reset the states of all shapes as unmarked
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->UnMarkShape();
for (int i = 0; i < g_numShapes; i++)
{
for (int j = i+1; j < g_numShapes; j++)
{
if (g_shapeList[i]->intersects(g_shapeList[j]))
{
g_shapeList[i]->MarkShape();
g_shapeList[j]->MarkShape();
}
}
}
}
}
//------------------------------------
void UpdateData()
{
// create viewport bounding rectangles to keep the shapes within the viewport
Point2d Winpos = { -1.0,0.0 };
Point2d Winsize = { 1.0 , g_windowHeight };
Point2d Winveldir = { 0,0 }; // dummy veldir
float Winvel = 0.0f; //not moving
Point3d Wincol = { 0,0,0 }; // dummy color
Rect WindowRectLeft(Winpos, Winsize, Winveldir, Winvel, Wincol);
Winpos.x = 0.0; Winpos.y = -1.0;
Winsize.x = g_windowWidth; Winsize.y = 1.0;
Rect WindowRectBottom(Winpos, Winsize, Winveldir, Winvel, Wincol);
Winpos.x = g_windowWidth; Winpos.y = 0.0;
Winsize.x = 1; Winsize.y = g_windowHeight;
Rect WindowRectRight(Winpos, Winsize, Winveldir, Winvel, Wincol);
Winpos.x = 0.0; Winpos.y = g_windowHeight;
Winsize.x = g_windowWidth; Winsize.y = 1.0f;
Rect WindowRectUp(Winpos, Winsize, Winveldir, Winvel, Wincol);
for (int i = 0; i < g_numShapes; i++)
{
// move the shape
g_shapeList[i]->move();
// if it bounces to the window walls, invert its veldir
if (g_shapeList[i]->intersects(&WindowRectLeft) ||
g_shapeList[i]->intersects(&WindowRectRight))
g_shapeList[i]->invert_xdir();
if (g_shapeList[i]->intersects(&WindowRectBottom) ||
g_shapeList[i]->intersects(&WindowRectUp))
g_shapeList[i]->invert_ydir();
}
}
//------------------------------------
void ChangeSize(GLsizei w, GLsizei h)
{
if(h == 0)
h = 1;
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
g_windowHeight = h;
g_windowWidth = w;
glOrtho(0, g_windowWidth, 0, g_windowHeight , 1.0f, -1.0f);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
//------------------------------------
void processNormalKeys(unsigned char key, int x, int y)
{
if (key == 'q') // PRESS 'q' to terminate the application
exit(0);
if(key=='r') // PRESS 'r' ket to reset the shapes
Initialize();
if (key == 's') // toggle between showing the intersections or not
g_bShowIntersection = g_bShowIntersection ? false: true;
}
//------------------------------------
void processSpecialKeys(int key, int x, int y)
{
switch(key) {
case GLUT_KEY_LEFT :
break;
case GLUT_KEY_RIGHT :
break;
case GLUT_KEY_UP:
// PRESSING UP ARROW KEY INCREASES THE SHAPE VELOCITIES
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->increase_vel();
break;
case GLUT_KEY_DOWN:
// PRESSING DOWN ARROW KEY DECREASES THE SHAPE VELOCITIES
for (int i = 0; i < g_numShapes; i++)
g_shapeList[i]->decrease_vel();
break;
}
}
//-------------------------------------
void display() {
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer
glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
UpdateData();
MarkObjects(g_bShowIntersection);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
for (int i= 0; i<g_numShapes; i++)
g_shapeList[i]->draw();
glutSwapBuffers();
}
//------------------------------------
int main(int argc, char* argv[])
{
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
glutInitWindowPosition(100,100);
glutInitWindowSize(WINDOWX, WINDOWY);
glutCreateWindow("COM102B - PA4");
// Register callback handler for window re-paint
glutDisplayFunc(display);
glutReshapeFunc(ChangeSize);
glutIdleFunc(display);
glutKeyboardFunc(processNormalKeys);
glutSpecialFunc(processSpecialKeys);
Initialize();
glutMainLoop(); // Enter infinitely event-processing loop
return 0;
}
Your problem is in these lines:
Rect *p1 = dynamic_cast<Rect*>(pshape);
Circ *p2 = dynamic_cast<Circ*>(pshape);
Unless you had inherited Rect from Circ or vice versa this is what makes your program crash, you can't cast your pShape to Circ if it is a Rect, so when you pass a Rect object to your function it will correctly cast to Rect* but it will fail with Circ* returning nullptr, so then when you try to access methods from p2 it will crash becouse you are accessing to invalid memory (0x00000000) :
if(p1)
{
float circleDistance_x = abs(p2->getPos().x - p1->getPos().x);
float circleDistance_y = abs(p2->getPos().y - p1->getPos().y);
if(circleDistance_x > (p1->getSize().x/2 + p2->getRad()))
return false;
if(circleDistance_y > (p1->getSize().y/2 + p2->getRad()))
return false;
if(circleDistance_x <= (p1->getSize().x/2))
return true;
if(circleDistance_y <= (p1->getSize().y/2))
return true;
float cornerDistance_sq = (circleDistance_x - (p1->getSize().x/2)) + (circleDistance_y - (p1->getSize().y/2))*(circleDistance_y - (p1->getSize().y/2));
return (cornerDistance_sq <= p2->getRad()^2);
}
So, you could simply just cast the first p1 pointer since the method is from circle, it's obvious that it was called from a Circ Object so there's no need for p2 pointer.
Rect *p1 = dynamic_cast<Rect*>(pshape);
if(p1)
{
float circleDistance_x = abs(getPos().x - p1->getPos().x);
float circleDistance_y = abs(getPos().y - p1->getPos().y);
if(circleDistance_x > (p1->getSize().x/2 + getRad()))
return false;
if(circleDistance_y > (p1->getSize().y/2 + getRad()))
return false;
if(circleDistance_x <= (p1->getSize().x/2))
return true;
if(circleDistance_y <= (p1->getSize().y/2))
return true;
float cornerDistance_sq = (circleDistance_x - (p1->getSize().x/2)) + (circleDistance_y - (p1->getSize().y/2))*(circleDistance_y - (p1->getSize().y/2));
return (cornerDistance_sq <= getRad()^2);
}
Also on the line:
return (cornerDistance_sq <= getRad()^2)
i think you are trying to get the radius square but this wont do it, what it is actually doing is
(cornerDistance_sq <= getRad()) ^ 2
becouse <= has greater precedence to ^, plus ^ is not a square operator it is a bitwise operator. So what you actually want is :
return cornerDistance_sq <= getRad() * getRad();
I am trying to render a fractal calculated using MPI. I used the answer to the following question as reference: sending blocks of 2D array in C using MPI
My problem is, that merge of data via MPI_Gatherv calculated by all the processes does not seem to work properly, because my main process always renders a black screen.
I have the following struct defined:
typedef struct Point {
float r,g,b,x,y;
} Point;
In my main I try to create an MPI_Datatype for the struct:
MPI_Datatype struct_type;
MPI_Datatype struct_members[1] = {MPI_FLOAT};
MPI_Aint offsets[1] = {0};
int struct_blengths[1] = {5};
int struct_items = 1;
MPI_Type_create_struct(struct_items, struct_blengths, offsets, struct_members, &struct_type);
MPI_Type_commit(&struct_type);
I have a global variable for the calculation result:
Point **mandelbrot;
The variable is allocated thusly before each frame is being recalculated:
if (proc_id == root) {
//Just a check if this is the first frame that is being rendered
if (s > 0) {
free(&(mandelbrot[0][0]));
free(mandelbrot);
}
s = W;
Point *p = (Point *) malloc(W * H * sizeof(Point));
mandelbrot = (Point **) malloc(W*sizeof(Point *));
for (int i = 0; i < W; i++) {
mandelbrot[i] = &(p[i*H]);
}
}
Here I try to create an array subtype using the Point struct (following the referenced answer as best I can):
//Width of the fractal to render
W = width;
//Height of the fractal
H = height;
//Chunk of width each process is responsible for [width / number of processes]
int segmentSize = (int) W / ntasks;
MPI_Datatype type, resizedtype;
int sizes[2] = {W,H}; /* size of global array */
int subsizes[2] = {segmentSize, H}; /* size of sub-region */
int starts[2] = {0,0};
MPI_Type_create_subarray(2, sizes, subsizes, starts, MPI_ORDER_C, struct_type, &type);
MPI_Type_create_resized(type, 0, H*sizeof(Point), &resizedtype);
MPI_Type_commit(&resizedtype);
Calculate the displacements and counts of blocks to send and allocate memory for the process' subarray:
int sendcounts[segmentSize*H];
int displs[segmentSize*H];
if (proc_id == root) {
for (int i=0; i<segmentSize*H; i++) sendcounts[i] = 1;
int disp = 0;
for (int i=0; i<segmentSize; i++) {
for (int j=0; j<H; j++) {
displs[i*H+j] = disp;
disp += 1;
}
disp += ((W/segmentSize)-1)*H;
}
}
Point *p = (Point *) malloc(segmentSize * H * sizeof(Point));
Point **segment;
segment = (Point **) malloc(segmentSize * sizeof(Point*));
for (int i = 0; i < segmentSize; i++) {
segment[i] = &(p[i*H]);
}
Following that I calculate the color of the Mandelbrot set for each point in the chunk:
int i;
float c[3], dX, dY;
for ( x = 0; x < segmentSize; x++) {
for ( y = 0; y < H; y++) {
//Iterate over the point
i = iterateMandelbrot(rM + x * dR, iM - y * dI);
// Get decimal coordinates for rendering <0,1>
dX = (x + segmentSize * proc_id) / W;
dY = y / H;
//Calculate color using Bernoulli Polynomials
makeColor(i, maxIterations, c);
segment[x][y].x = (float) dX;
segment[x][y].y = (float) dY;
segment[x][y].r = (float) c[0];
segment[x][y].g = (float) c[1];
segment[x][y].b = (float) c[2];
}
}
Lastly I try to gather the chunks into the mandelbort variable for the root process to render:
int buffsize = (int) segmentSize * H;
MPI_Gatherv(&(segment[0][0]), W*H/(buffsize), struct_type,
&(mandelbrot[0][0]), sendcounts, displs, resizedtype,
root, MPI_COMM_WORLD);
MPI_Type_free(&resizedtype);
Ok so the problem is now that no data seems to be written into the mandelbrot variable as my main process renders a black screen. Without using MPI the code works so the problem lies somewhere in the MPI_Gatherv call or maybe the way I am allocating the arrays. I realize there might be some memory leak associated with the mandelbrot set or the local segment arrays but that is not my main concern at the moment. Can you see what I am doing wrong here? Any help is appreciated!
My goal is simple: I want to create a rendering system in C++ that can draw thousands of bitmaps on screen. I have been trying to use threads to speed up the process but to no avail. In most cases, I have actually slowed down performance by using multiple threads. I am using this project as an educational exercise by not using hardware acceleration. That said, my question is this:
What is the best way to use several threads to accept a massive list of images to be drawn onto the screen and render them at break-neck speeds? I know that I won’t be able to create a system that can rival hardware accelerated graphics, but I believe that my idea is still feasible because the operation is so simple: copying pixels from one memory location to another.
My renderer design uses three core blitting operations: position, rotation, and scale of a bitmap image. I have it set up to only rotate an image when needed, and only scale an image when needed.
I have gone through several designs for this system. All of them too slow to get the job done (300 64x64 bitmaps at barely 60fps).
Here are the designs I have tried:
Immediately drawing a source bitmap on a destination bitmap for every image on screen (moderate speed).
Creating workers that accept a draw instruction and immediately begin working on it while other workers receive their instructions also (slowest).
Workers that receive packages of several instructions at a time (slower).
Saving all drawing instructions up and then parting them up in one swoop to several workers while other tasks (in theory) are being done (slowest).
Here is the bitmap class I am using to blit bitmaps onto each other:
class Bitmap
{
public:
Bitmap(int w, int h)
{
width = w;
height = h;
size = w * h;
pixels = new unsigned int[size];
}
virtual ~Bitmap()
{
if (pixels != 0)
{
delete[] pixels;
pixels = 0;
}
}
void blit(Bitmap *bmp, float x, float y, float rot, float sclx,
float scly)
{
// Position only
if (rot == 0 && sclx == 1 && scly == 1)
{
blitPos(bmp, x, y);
return;
}
// Rotate only
else if (rot != 0 && sclx == 1 && scly == 1)
{
blitRot(bmp, x, y, rot);
return;
}
// Scale only
else if (rot == 0 && (sclx != 1 || scly != 1))
{
blitScl(bmp, x, y, sclx, scly);
return;
}
/////////////////////////////////////////////////////////////////////////////
// If it is not one of those, you have to do all three... :D
/////////////////////////////////////////////////////////////////////////////
// Create a bitmap that is scaled to the new size.
Bitmap tmp((int)(bmp->width * sclx), (int)(bmp->height * scly));
// Find how much each pixel steps:
float step_x = (float)bmp->width / (float)tmp.width;
float step_y = (float)bmp->height / (float)tmp.height;
// Fill the scaled image with pixels!
float inx = 0;
int xOut = 0;
while (xOut < tmp.width)
{
float iny = 0;
int yOut = 0;
while (yOut < tmp.height)
{
unsigned int sample = bmp->pixels[
(int)(std::floor(inx) + std::floor(iny) * bmp->width)
];
tmp.drawPixel(xOut, yOut, sample);
iny += step_y;
yOut++;
}
inx += step_x;
xOut++;
}
blitRot(&tmp, x, y, rot);
}
void drawPixel(int x, int y, unsigned int color)
{
if (x > width || y > height || x < 0 || y < 0)
return;
if (color == 0x00000000)
return;
int index = x + y * width;
if (index >= 0 && index <= size)
pixels[index] = color;
}
unsigned int getPixel(int x, int y)
{
return pixels[x + y * width];
}
void clear(unsigned int color)
{
std::fill(&pixels[0], &pixels[size], color);
}
private:
void blitPos(Bitmap *bmp, float x, float y)
{
// Don't draw if coordinates are already past edges
if (x > width || y > height || y + bmp->height < 0 || x + bmp->width < 0)
return;
int from;
int to;
int destfrom;
int destto;
for (int i = 0; i < bmp->height; i++)
{
from = i * bmp->width;
to = from + bmp->width;
//////// Caps
// Bitmap is being drawn past the right edge
if (x + bmp->width > width)
{
int cap = bmp->width - ((x + bmp->width) - width);
to = from + cap;
}
// Bitmap is being drawn past the left edge
else if (x + bmp->width < bmp->width)
{
int cap = bmp->width + x;
from += (bmp->width - cap);
to = from + cap;
}
//////// Destination Maths
if (x < 0)
{
destfrom = (y + i) * width;
destto = destfrom + (bmp->width + x);
}
else
{
destfrom = x + (y + i) * width;
destto = destfrom + bmp->width;
}
// Bitmap is being drawn past either top or bottom edges
if (y + i > height - 1)
{
continue;
}
if (destfrom > size || destfrom < 0)
{
continue;
}
memcpy(&pixels[destfrom], &bmp->pixels[from], sizeof(unsigned int) * (to - from));
}
}
void blitRot(Bitmap *bmp, float x, float y, float rot)
{
float sine = std::sin(-rot);
float cosine = std::cos(-rot);
int x1 = (int)(-bmp->height * sine);
int y1 = (int)(bmp->height * cosine);
int x2 = (int)(bmp->width * cosine - bmp->height * sine);
int y2 = (int)(bmp->height * cosine + bmp->width * sine);
int x3 = (int)(bmp->width * cosine);
int y3 = (int)(bmp->width * sine);
int minx = (int)std::min(0, std::min(x1, std::min(x2, x3)));
int miny = (int)std::min(0, std::min(y1, std::min(y2, y3)));
int maxx = (int)std::max(0, std::max(x1, std::max(x2, x3)));
int maxy = (int)std::max(0, std::max(y1, std::max(y2, y3)));
int w = maxx - minx;
int h = maxy - miny;
int srcx;
int srcy;
int dest_x;
int dest_y;
unsigned int color;
for (int sy = miny; sy < maxy; sy++)
{
for (int sx = minx; sx < maxx; sx++)
{
srcx = sx * cosine + sy * sine;
srcy = sy * cosine - sx * sine;
dest_x = x + sx;
dest_y = y + sy;
if (dest_x <= width - 1 && dest_y <= height - 1
&& dest_x >= 0 && dest_y >= 0)
{
color = 0;
// Only grab a pixel if it is inside of the src image
if (srcx < bmp->width && srcy < bmp->height && srcx >= 0 &&
srcy >= 0)
color = bmp->getPixel(srcx, srcy);
// Only this pixel if it is not completely transparent:
if (color & 0xFF000000)
// Only if the pixel is somewhere between 0 and the bmp size
if (0 < srcx < bmp->width && 0 < srcy < bmp->height)
drawPixel(x + sx, y + sy, color);
}
}
}
}
void blitScl(Bitmap *bmp, float x, float y, float sclx, float scly)
{
// Create a bitmap that is scaled to the new size.
int finalwidth = (int)(bmp->width * sclx);
int finalheight = (int)(bmp->height * scly);
// Find how much each pixel steps:
float step_x = (float)bmp->width / (float)finalwidth;
float step_y = (float)bmp->height / (float)finalheight;
// Fill the scaled image with pixels!
float inx = 0;
int xOut = 0;
float iny;
int yOut;
while (xOut < finalwidth)
{
iny = 0;
yOut = 0;
while (yOut < finalheight)
{
unsigned int sample = bmp->pixels[
(int)(std::floor(inx) + std::floor(iny) * bmp->width)
];
drawPixel(xOut + x, yOut + y, sample);
iny += step_y;
yOut++;
}
inx += step_x;
xOut++;
}
}
public:
int width;
int height;
int size;
unsigned int *pixels;
};
Here is some code showing the latest method I have tried: saving up all instructions and then giving them to workers once they have all been received:
class Instruction
{
public:
Instruction() {}
Instruction(Bitmap* out, Bitmap* in, float x, float y, float rot,
float sclx, float scly)
: outbuffer(out), inbmp(in), x(x), y(y), rot(rot),
sclx(sclx), scly(scly)
{ }
~Instruction()
{
outbuffer = nullptr;
inbmp = nullptr;
}
public:
Bitmap* outbuffer;
Bitmap* inbmp;
float x, y, rot, sclx, scly;
};
Layer Class:
class Layer
{
public:
bool empty()
{
return instructions.size() > 0;
}
public:
std::vector<Instruction> instructions;
int pixel_count;
};
Worker Thread Class:
class Worker
{
public:
void start()
{
done = false;
work_thread = std::thread(&Worker::processData, this);
}
void processData()
{
while (true)
{
controller.lock();
if (done)
{
controller.unlock();
break;
}
if (!layers.empty())
{
for (int i = 0; i < layers.size(); i++)
{
for (int j = 0; j < layers[i].instructions.size(); j++)
{
Instruction* inst = &layers[i].instructions[j];
inst->outbuffer->blit(inst->inbmp, inst->x, inst->y, inst->rot, inst->sclx, inst->scly);
}
}
layers.clear();
}
controller.unlock();
}
}
void finish()
{
done = true;
}
public:
bool done;
std::thread work_thread;
std::mutex controller;
std::vector<Layer> layers;
};
Finally, the Render Manager Class:
class RenderManager
{
public:
RenderManager()
{
workers.reserve(std::thread::hardware_concurrency());
for (int i = 0; i < 1; i++)
{
workers.emplace_back();
workers.back().start();
}
}
void layer()
{
layers.push_back(current_layer);
current_layer = Layer();
}
void blit(Bitmap* out, Bitmap* in, float x, float y, float rot, float sclx, float scly)
{
current_layer.instructions.emplace_back(out, in, x, y, rot, sclx, scly);
}
void processInstructions()
{
if (layers.empty())
layer();
lockall();
int index = 0;
for (int i = 0; i < layers.size(); i++)
{
// Evenly distribute the layers in a round-robin fashion
Layer l = layers[i];
workers[index].layers.push_back(layers[i]);
index++;
if (index >= workers.size()) index = 0;
}
layers.clear();
unlockall();
}
void lockall()
{
for (int i = 0; i < workers.size(); i++)
{
workers[i].controller.lock();
}
}
void unlockall()
{
for (int i = 0; i < workers.size(); i++)
{
workers[i].controller.unlock();
}
}
void finish()
{
// Wait until every worker is done rendering
lockall();
// At this point, we know they have nothing more to draw
unlockall();
}
void endRendering()
{
for (int i = 0; i < workers.size(); i++)
{
// Send each one an exit code
workers[i].finish();
}
// Let the workers finish and then return
for (int i = 0; i < workers.size(); i++)
{
workers[i].work_thread.join();
}
}
private:
std::vector<Worker> workers;
std::vector<Layer> layers;
Layer current_layer;
};
Here is a screenshot of what the 3rd method I tried, and it's results:
Sending packages of draw instructions
What would really be helpful is that if someone could simply point me in the right direction in regards to what method I should try. I have tried these four methods and have failed, so I stand before those who have done greater things than I for help. The least intelligent person in the room is the one that does not ask questions because his pride does not permit it. Please keep in mind though, this is my first question ever on Stack Overflow.
We have an image like:
We have 4 coordinates top:10, bottom:10, left:10, right:10 we have resize to values like newWidth:100, newHeight:35 we have some SDL_Rect Sprite which was generated from some SDL_Surface *button how to performe on that Sprite such resize transformations?
So how to inplement 9-slice scaling in SDL?
I've made a demo project performing 9 slice rendering using c and sdl-2 here: https://github.com/cxong/sdl2-9-slice
Take a look at the render() function and copy it if you like - it's permissively licensed.
The key is to use the srcrect and dstrect parameters of SDL_RenderCopy() - the former is which part of the source texture to render, the latter which part of the destination (render target) to render into.
For 9 slice, the corners are copied as-is; for the middle parts, depending on how you want to render - stretched or repeated - the srcrect will be the same, but dstrect will stretch or repeat.
Another thing is that SDL does not do texture repeating (yet). So if you want to render as repeat mode, you need to use a loop.
Here's the function in case the project dies:
int render(
SDL_Renderer *renderer, SDL_Surface *s, SDL_Texture *t,
int x, int y, int top, int bottom, int left, int right, int w, int h,
bool repeat)
{
const int srcX[] = {0, left, s->w - right};
const int srcY[] = {0, top, s->h - bottom};
const int srcW[] = {left, s->w - right - left, right};
const int srcH[] = {top, s->h - bottom - top, bottom};
const int dstX[] = {x, x + left, x + w - right, x + w};
const int dstY[] = {y, y + top, y + h - bottom, y + h};
const int dstW[] = {left, w - right - left, right};
const int dstH[] = {top, h - bottom - top, bottom};
SDL_Rect src;
SDL_Rect dst;
for (int i = 0; i < 3; i++)
{
src.x = srcX[i];
src.w = srcW[i];
dst.w = repeat ? srcW[i] : dstW[i];
for (dst.x = dstX[i]; dst.x < dstX[i + 1]; dst.x += dst.w)
{
if (dst.x + dst.w > dstX[i + 1])
{
src.w = dst.w = dstX[i + 1] - dst.x;
}
for (int j = 0; j < 3; j++)
{
src.y = srcY[j];
src.h = srcH[j];
dst.h = repeat ? srcH[j] : dstH[j];
for (dst.y = dstY[j]; dst.y < dstY[j + 1]; dst.y += dst.h)
{
if (dst.y + dst.h > dstY[j + 1])
{
src.h = dst.h = dstY[j + 1] - dst.y;
}
const int res = SDL_RenderCopy(renderer, t, &src, &dst);
if (res != 0)
{
return res;
}
}
}
}
}
return 0;
}