Compare commits

..
8 Commits
Author SHA1 Message Date
josh 1a5b1292ee Add -j option 2026-06-18 08:40:48 -04:00
josh 36e12d1e90 Render multithreaded locally by default 2026-06-18 08:30:20 -04:00
josh aad02e522d Avoid bison compiler warning for yynerrs 2026-06-15 08:31:15 -04:00
josh 8f09415b37 Remove use of std::binary_function 2026-06-15 08:27:20 -04:00
josh d1b558a554 Fix refptr build warnings 2026-06-15 08:21:50 -04:00
josh acfb883f66 Fix flex build error 2026-06-15 08:20:14 -04:00
josh ede4f6235a Replace freeimage with stb_image 2026-06-15 00:13:13 -04:00
josh 0003c74f3b Use fl LIBS instead of :libfl.a 2026-06-14 23:51:15 -04:00
13 changed files with 8187 additions and 96 deletions

No files matched your search

-2
View File
@@ -2,9 +2,7 @@ configure do
check_cxx_compiler check_cxx_compiler
check_program "flex" check_program "flex"
check_program "bison" check_program "bison"
check_lib ":libfl.a"
check_lib "pthread" check_lib "pthread"
check_lib "freeimage"
end end
env do |env| env do |env|
+26 -27
View File
@@ -6,6 +6,7 @@
#include "Scene.h" #include "Scene.h"
#include "Light.h" #include "Light.h"
#include "util/stb_image.h"
#include "parser/parser.h" #include "parser/parser.h"
#include "parser/nodes.h" #include "parser/nodes.h"
#include "util/Polygon.h" #include "util/Polygon.h"
@@ -347,19 +348,23 @@ refptr<Material> Scene::processMaterial(refptr<Node> node)
else if ( typeid(**it) == typeid(TextureNode) ) else if ( typeid(**it) == typeid(TextureNode) )
{ {
string filename = (*it)->getString(); string filename = (*it)->getString();
FIBITMAP * fib = NULL; Texture * texture = NULL;
if (m_textures.find(filename) != m_textures.end()) if (m_textures.find(filename) != m_textures.end())
{ {
/* texture already loaded */ /* texture already loaded */
fib = m_textures[filename]; texture = m_textures[filename];
} }
else else
{ {
fib = loadTexture((*it)->getString()); texture = loadTexture(filename);
if (texture != NULL)
{
m_textures[filename] = texture;
}
} }
if (fib != NULL) if (texture != NULL)
{ {
material->setTexture(fib); material->setTexture(texture);
} }
} }
} }
@@ -811,30 +816,24 @@ void Scene::processShapeDefinition(refptr<Node> node)
m_transforms.pop(); m_transforms.pop();
} }
FIBITMAP * Scene::loadTexture(const std::string & filename) Texture * Scene::loadTexture(const std::string & filename)
{ {
FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(filename.c_str(), 0); int width = 0;
if (fif == FIF_UNKNOWN) int height = 0;
int channels = 0;
/* request 4 channels so the data is always tightly packed RGBA */
unsigned char * data = stbi_load(filename.c_str(), &width, &height,
&channels, 4);
if (data == NULL)
{ {
fif = FreeImage_GetFIFFromFilename(filename.c_str()); cerr << "Error: image \"" << filename << "\" could not be loaded: "
if (fif == FIF_UNKNOWN) << stbi_failure_reason() << endl;
{
cerr << "Error: couldn't determine image format for \""
<< filename << "\"" << endl;
return NULL;
}
}
if (!FreeImage_FIFSupportsReading(fif))
{
cerr << "Error: image format of \"" << filename
<< "\" does not support reading" << endl;
return NULL; return NULL;
} }
FIBITMAP * fib = FreeImage_Load(fif, filename.c_str(), 0); Texture * texture = new Texture;
if (fib == NULL) texture->width = width;
{ texture->height = height;
cerr << "Error: image \"" << filename << "\" could not be loaded" texture->channels = 4;
<< endl; texture->data = data;
} return texture;
return fib;
} }
+5 -4
View File
@@ -7,11 +7,11 @@
#include <utility> /* pair */ #include <utility> /* pair */
#include <map> #include <map>
#include <algorithm> /* sort() */ #include <algorithm> /* sort() */
#include <functional> /* binary_function */
#include <typeinfo> /* typeid operator support */ #include <typeinfo> /* typeid operator support */
#include "Scene.h" #include "Scene.h"
#include "BMP.h" #include "BMP.h"
#include "util/stb_image.h"
#include "util/Color.h" #include "util/Color.h"
#include "shapes/Shape.h" #include "shapes/Shape.h"
#include "Light.h" #include "Light.h"
@@ -75,12 +75,13 @@ Scene::Scene(const map<string, const char *> & options,
Scene::~Scene() Scene::~Scene()
{ {
/* clean up any textures loaded with freeimage */ /* clean up any textures loaded with stb_image */
for (std::map< std::string, FIBITMAP * >::iterator it = m_textures.begin(); for (std::map< std::string, Texture * >::iterator it = m_textures.begin();
it != m_textures.end(); it != m_textures.end();
it++) it++)
{ {
FreeImage_Unload(it->second); stbi_image_free(it->second->data);
delete it->second;
} }
} }
+2 -4
View File
@@ -8,8 +8,6 @@
#include <utility> #include <utility>
#include <stack> #include <stack>
#include <FreeImage.h>
#include "util/refptr.h" #include "util/refptr.h"
#include "util/Ray.h" #include "util/Ray.h"
#include "util/Color.h" #include "util/Color.h"
@@ -82,7 +80,7 @@ class Scene
std::vector<ShapeRef> processForNode(refptr<Node> node); std::vector<ShapeRef> processForNode(refptr<Node> node);
void processMaterialDefinition(refptr<Node> node); void processMaterialDefinition(refptr<Node> node);
void processShapeDefinition(refptr<Node> node); void processShapeDefinition(refptr<Node> node);
FIBITMAP * loadTexture(const std::string & filename); Texture * loadTexture(const std::string & filename);
/* rendering parameters */ /* rendering parameters */
int m_width; int m_width;
@@ -104,7 +102,7 @@ class Scene
double m_sample_span; double m_sample_span;
double m_half_sample_span; double m_half_sample_span;
std::map< std::string, refptr<Material> > m_materials; std::map< std::string, refptr<Material> > m_materials;
std::map< std::string, FIBITMAP * > m_textures; std::map< std::string, Texture * > m_textures;
}; };
#endif #endif
+117 -28
View File
@@ -9,8 +9,10 @@
#include <iostream> #include <iostream>
#include <string> #include <string>
#include <map> #include <map>
#include <vector>
#include <FreeImage.h> #include <pthread.h>
#include <atomic>
#include "Scene.h" #include "Scene.h"
#include "distrib/distrib.h" #include "distrib/distrib.h"
@@ -18,6 +20,111 @@
using namespace std; using namespace std;
/*
* Number of contiguous pixels each worker thread claims at a time. Pulling
* work in small chunks (rather than whole rows) keeps the load balanced even
* when individual rows vary wildly in render cost, while keeping the claimed
* pixels contiguous so cache locality and dispatch overhead stay close to the
* row-based baseline. See benchmarks: 64 was the sweet spot.
*/
#define RENDER_CHUNK_SIZE 64
/*
* State shared by the local multithreaded renderer. Worker threads pull
* fixed-size chunks of pixels off a lock-free atomic cursor until the image
* is complete.
*/
typedef struct
{
Scene * scene;
unsigned char * data;
int width;
int total_pixels;
std::atomic<int> next_pixel; /* dispatch cursor: next unclaimed pixel */
std::atomic<int> pixels_done; /* pixels finished, for progress */
std::atomic<int> last_permille; /* last tenth-of-a-percent printed */
} render_thread_state_t;
static void * render_thread(void * varg)
{
render_thread_state_t * state = (render_thread_state_t *) varg;
const int width = state->width;
const int total_pixels = state->total_pixels;
for (;;)
{
/* atomically claim the next chunk of contiguous pixels */
int start = state->next_pixel.fetch_add(RENDER_CHUNK_SIZE,
std::memory_order_relaxed);
if (start >= total_pixels)
break;
int end = start + RENDER_CHUNK_SIZE;
if (end > total_pixels)
end = total_pixels;
for (int pixel = start; pixel < end; pixel++)
{
int y = pixel / width;
int x = pixel % width;
state->scene->renderPixel(x, y, &state->data[3 * pixel]);
}
/* report progress without locking: of the threads that advance the
* tenth-of-a-percent counter, only the one that wins the update prints,
* so the display stays monotonic and free of interleaved output */
int done = state->pixels_done.fetch_add(end - start,
std::memory_order_relaxed)
+ (end - start);
int permille = (int) (1000L * done / total_pixels);
int prev = state->last_permille.load(std::memory_order_relaxed);
while (permille > prev)
{
if (state->last_permille.compare_exchange_weak(
prev, permille, std::memory_order_relaxed))
{
printf("\e[8D%2.1f%%", permille / 10.0);
fflush(stdout);
break;
}
/* prev reloaded by compare_exchange_weak; re-test permille > prev */
}
}
return NULL;
}
static void renderThreaded(Scene & scene,
unsigned char * data,
int width,
int height,
int num_threads)
{
/* a non-positive override means auto-detect the available cores */
if (num_threads < 1)
{
num_threads = sysconf(_SC_NPROCESSORS_ONLN);
if (num_threads < 1)
num_threads = 1;
}
render_thread_state_t state;
state.scene = &scene;
state.data = data;
state.width = width;
state.total_pixels = width * height;
state.next_pixel.store(0, std::memory_order_relaxed);
state.pixels_done.store(0, std::memory_order_relaxed);
state.last_permille.store(0, std::memory_order_relaxed);
vector<pthread_t> threads(num_threads);
for (int i = 0; i < num_threads; i++)
pthread_create(&threads[i], NULL, render_thread, &state);
for (int i = 0; i < num_threads; i++)
pthread_join(threads[i], NULL);
printf("\e[8D");
}
void usage(const char * progname) void usage(const char * progname)
{ {
cout << "Usage: " << progname << " [options] <scene-file>" << endl; cout << "Usage: " << progname << " [options] <scene-file>" << endl;
@@ -30,6 +137,7 @@ void usage(const char * progname)
cout << " -d|--max-depth <max-recursion-depth>" << endl; cout << " -d|--max-depth <max-recursion-depth>" << endl;
cout << " -a|--ambient-occlusion <ambient-occlusion-level>" << endl; cout << " -a|--ambient-occlusion <ambient-occlusion-level>" << endl;
cout << " -p|--preview (means -w400 -h300 -m1 -d8 -a0)" << endl; cout << " -p|--preview (means -w400 -h300 -m1 -d8 -a0)" << endl;
cout << " -j|--threads <num-threads> (default: number of cores)" << endl;
cout << " --hosts <hosts-file>" << endl; cout << " --hosts <hosts-file>" << endl;
exit(42); exit(42);
} }
@@ -92,6 +200,7 @@ int main(int argc, char * argv[])
unsigned char * data = NULL; unsigned char * data = NULL;
const char * output_file_name = "fart.bmp"; const char * output_file_name = "fart.bmp";
bool child = false; bool child = false;
int num_threads = 0; /* 0 means auto-detect available cores */
static const struct option long_options[] = { static const struct option long_options[] = {
{ "ambient-occlusion", required_argument, NULL, 'a' }, { "ambient-occlusion", required_argument, NULL, 'a' },
@@ -102,6 +211,7 @@ int main(int argc, char * argv[])
{ "field-of-view", required_argument, NULL, 'f' }, { "field-of-view", required_argument, NULL, 'f' },
{ "max-depth", required_argument, NULL, 'd' }, { "max-depth", required_argument, NULL, 'd' },
{ "preview", no_argument, NULL, 'p' }, { "preview", no_argument, NULL, 'p' },
{ "threads", required_argument, NULL, 'j' },
{ "help", no_argument, NULL, 256 }, { "help", no_argument, NULL, 256 },
{ "host", required_argument, NULL, 257 }, { "host", required_argument, NULL, 257 },
{ "port", required_argument, NULL, 258 }, { "port", required_argument, NULL, 258 },
@@ -110,7 +220,7 @@ int main(int argc, char * argv[])
{ NULL, 0, NULL, 0 } { NULL, 0, NULL, 0 }
}; };
while ((opt = getopt_long(argc, argv, "a:o:w:h:m:f:d:p", while ((opt = getopt_long(argc, argv, "a:o:w:h:m:f:d:pj:",
long_options, &option_index)) != -1) long_options, &option_index)) != -1)
{ {
switch (opt) switch (opt)
@@ -156,6 +266,9 @@ int main(int argc, char * argv[])
scene_options["max-depth"] = "8"; scene_options["max-depth"] = "8";
client_options.push_back("--preview"); client_options.push_back("--preview");
break; break;
case 'j':
num_threads = atoi(optarg);
break;
case 256: case 256:
usage(argv[0]); usage(argv[0]);
break; break;
@@ -188,8 +301,6 @@ int main(int argc, char * argv[])
srand(time(NULL)); srand(time(NULL));
FreeImage_Initialise(FALSE);
const char * filename = argv[optind]; const char * filename = argv[optind];
client_options.push_back(filename); client_options.push_back(filename);
Scene scene(scene_options, filename); Scene scene(scene_options, filename);
@@ -278,28 +389,8 @@ int main(int argc, char * argv[])
} }
else else
{ {
int total_pixels = height * width; /* local multithreaded render; num_threads of 0 auto-detects cores */
int total_pixels_1000 = total_pixels / 1000; renderThreaded(scene, data, width, height, num_threads);
if (total_pixels_1000 < 1)
total_pixels_1000 = 1;
int pixel_num = 0;
/* "sequential" version */
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int pixel = i * width + j;
scene.renderPixel(j, i, &data[3 * pixel]);
pixel_num++;
if (pixel_num % total_pixels_1000 == 0)
{
double pct = 100.0 * pixel_num / (double) total_pixels;
printf("\e[8D%2.1f%%", pct);
fflush(stdout);
}
}
}
printf("\e[8D");
} }
gettimeofday(&after, NULL); /* stop timing */ gettimeofday(&after, NULL); /* stop timing */
@@ -338,8 +429,6 @@ int main(int argc, char * argv[])
cout << endl; cout << endl;
} }
FreeImage_DeInitialise();
exit(0); exit(0);
return 0; return 0;
} }
+1
View File
@@ -1,5 +1,6 @@
%option nounput %option nounput
%option noyywrap
%option bison-locations %option bison-locations
%{ %{
+6 -5
View File
@@ -19,11 +19,6 @@ extern FILE * yyin;
void errFunc(const char * str, YYLTYPE * yyllocp); void errFunc(const char * str, YYLTYPE * yyllocp);
int yywrap()
{
return 1;
}
static refptr<Node> parsed_scene_node; static refptr<Node> parsed_scene_node;
refptr<Scope> parser_scope; refptr<Scope> parser_scope;
@@ -33,6 +28,12 @@ refptr<Scope> parser_scope;
%locations %locations
%define parse.error verbose %define parse.error verbose
%initial-action {
/* Reference yynerrs so the generated parser does not warn that it is
set but never used; this grammar has no error-recovery rules. */
(void) yynerrs;
}
%token PLUS; %token PLUS;
%token MINUS; %token MINUS;
%token TIMES; %token TIMES;
-4
View File
@@ -1,7 +1,6 @@
#include "Shape.h" #include "Shape.h"
#include <algorithm> /* sort() */ #include <algorithm> /* sort() */
#include <functional> /* binary_function */
#include <utility> #include <utility>
using namespace std; using namespace std;
@@ -28,9 +27,6 @@ void Shape::setMaterial(refptr<Material> material)
} }
class BoolIntersectionComparator class BoolIntersectionComparator
: public std::binary_function<Shape::BoolIntersection,
Shape::BoolIntersection,
bool>
{ {
public: public:
BoolIntersectionComparator(const Vector & refPoint) BoolIntersectionComparator(const Vector & refPoint)
+4 -5
View File
@@ -2,8 +2,7 @@
#ifndef MATERIAL_H #ifndef MATERIAL_H
#define MATERIAL_H MATERIAL_H #define MATERIAL_H MATERIAL_H
#include <FreeImage.h> #include "Texture.h"
#include "Color.h" #include "Color.h"
class Material class Material
@@ -55,8 +54,8 @@ class Material
void setTransparency(double t) { m_transparency = t; } void setTransparency(double t) { m_transparency = t; }
double getTransparency() const { return m_transparency; } double getTransparency() const { return m_transparency; }
void setTexture(FIBITMAP * fib) { m_texture = fib; } void setTexture(Texture * texture) { m_texture = texture; }
FIBITMAP * getTexture() const { return m_texture; } Texture * getTexture() const { return m_texture; }
protected: protected:
Color m_ambient_color; Color m_ambient_color;
@@ -66,7 +65,7 @@ class Material
double m_reflectance; double m_reflectance;
double m_refraction; double m_refraction;
double m_transparency; double m_transparency;
FIBITMAP * m_texture; Texture * m_texture;
}; };
#endif #endif
+16
View File
@@ -0,0 +1,16 @@
#ifndef TEXTURE_H
#define TEXTURE_H TEXTURE_H
/* A loaded image texture. Pixel data is stored as tightly packed RGBA
* bytes (4 channels) and is owned by this struct -- free it with
* stbi_image_free(). */
struct Texture
{
int width;
int height;
int channels;
unsigned char * data;
};
#endif
+17 -17
View File
@@ -3,17 +3,18 @@
#define REFPTR_H REFPTR_H #define REFPTR_H REFPTR_H
#include <stdlib.h> /* NULL */ #include <stdlib.h> /* NULL */
#include <atomic> /* std::atomic */
template <typename T> template <typename T>
class refptr class refptr
{ {
public: public:
refptr<T>(); refptr();
refptr<T>(T * ptr); refptr(T * ptr);
refptr<T>(const refptr<T> & orig); refptr(const refptr<T> & orig);
refptr<T> & operator=(const refptr<T> & orig); refptr & operator=(const refptr<T> & orig);
refptr<T> & operator=(T * ptr); refptr & operator=(T * ptr);
~refptr<T>(); ~refptr();
T & operator*() const; T & operator*() const;
T * operator->() const; T * operator->() const;
bool isNull() const { return m_ptr == NULL; } bool isNull() const { return m_ptr == NULL; }
@@ -25,7 +26,10 @@ class refptr
void destroy(); void destroy();
T * m_ptr; T * m_ptr;
int * m_refCount; /* reference count is atomic so that refptr copies may be made
* concurrently from multiple threads (e.g. the multithreaded
* renderer) without corrupting the count */
std::atomic<int> * m_refCount;
}; };
template <typename T> refptr<T>::refptr() template <typename T> refptr<T>::refptr()
@@ -37,8 +41,7 @@ template <typename T> refptr<T>::refptr()
template <typename T> refptr<T>::refptr(T * ptr) template <typename T> refptr<T>::refptr(T * ptr)
{ {
m_ptr = ptr; m_ptr = ptr;
m_refCount = new int; m_refCount = new std::atomic<int>(1);
*m_refCount = 1;
} }
template <typename T> refptr<T>::refptr(const refptr<T> & orig) template <typename T> refptr<T>::refptr(const refptr<T> & orig)
@@ -57,8 +60,7 @@ template <typename T> refptr<T> & refptr<T>::operator=(T * ptr)
{ {
destroy(); destroy();
m_ptr = ptr; m_ptr = ptr;
m_refCount = new int; m_refCount = new std::atomic<int>(1);
*m_refCount = 1;
return *this; return *this;
} }
@@ -67,7 +69,7 @@ template <typename T> void refptr<T>::cloneFrom(const refptr<T> & orig)
this->m_ptr = orig.m_ptr; this->m_ptr = orig.m_ptr;
this->m_refCount = orig.m_refCount; this->m_refCount = orig.m_refCount;
if (m_refCount != NULL) if (m_refCount != NULL)
(*m_refCount)++; m_refCount->fetch_add(1, std::memory_order_relaxed);
} }
template <typename T> refptr<T>::~refptr() template <typename T> refptr<T>::~refptr()
@@ -79,15 +81,13 @@ template <typename T> void refptr<T>::destroy()
{ {
if (m_refCount != NULL) if (m_refCount != NULL)
{ {
if (*m_refCount <= 1) /* fetch_sub returns the value prior to the decrement; if it was 1
* then this was the last reference and we own the cleanup */
if (m_refCount->fetch_sub(1, std::memory_order_acq_rel) == 1)
{ {
delete m_ptr; delete m_ptr;
delete m_refCount; delete m_refCount;
} }
else
{
(*m_refCount)--;
}
} }
} }
+5
View File
@@ -0,0 +1,5 @@
/* Single translation unit that compiles the stb_image implementation. */
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
+7988
View File
File diff suppressed because it is too large. Load diff