Skip to content

Commit c9bf459

Browse files
committed
Even better accumulative samples
1 parent ceb008b commit c9bf459

File tree

4 files changed

+321
-154
lines changed

4 files changed

+321
-154
lines changed

src/302_raytracer/camera.h

Lines changed: 121 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -452,30 +452,29 @@ class Camera
452452
}
453453

454454
/**
455-
* @brief Renders progressively with SDL2 window display showing incremental quality improvements
455+
* @brief Interactive SDL rendering with continuous sample accumulation
456456
*
457-
* This method displays the image in an SDL window while rendering with increasing sample rates.
458-
* It starts with low sample counts for quick preview and progressively increases quality.
459-
* Interactive camera controls with automatic re-rendering:
457+
* This method displays the ray traced image in an SDL window while continuously
458+
* accumulating samples for progressive quality improvement. Uses true CUDA-based
459+
* sample accumulation for optimal performance and smooth visual progression.
460+
*
461+
* Interactive camera controls:
460462
* - Left mouse button: Rotate camera (orbit around lookat point)
461463
* - Right mouse button: Pan camera (move lookat point)
462464
* - Mouse wheel: Zoom in/out (change camera distance)
463-
* - Any camera movement automatically triggers re-rendering from 8 samples
465+
* - Any camera movement automatically restarts rendering from scratch
464466
* - ESC/Close window: Exit
465467
*
466-
* The interactive mode starts rendering from 8 samples for fast response.
467-
* Any camera movement interrupts current rendering and immediately starts a new render.
468-
* Maximum sample count capped at 256 for interactive responsiveness.
469-
* For highest quality renders, use the regular CUDA rendering mode.
470-
*
471-
* Tiled rendering: Frame is divided into fixed 8x8 block grid (64 tiles) for consistent
472-
* visual appearance and responsive interaction at all quality levels.
473-
* 500ms delay between quality stages to allow viewing each stage clearly.
468+
* The renderer starts with samples_per_batch samples for fast preview,
469+
* then continuously adds samples_per_batch samples until reaching max_samples.
470+
* Each batch is rendered on the GPU and accumulated into a floating-point buffer,
471+
* providing smooth quality improvement without discrete quality jumps.
474472
*
475-
* @param image The final image buffer to store the highest quality render
476-
* @param sample_stages Vector of sample counts for progressive rendering (default: {8, 16, 32, 64, 128, 256})
473+
* @param image The final image buffer to store the render
474+
* @param max_samples Maximum total samples to accumulate (default: 1024)
475+
* @param samples_per_batch Number of samples to add per batch (default: 8)
477476
*/
478-
void renderPixelsSDLProgressive(vector<unsigned char> &image, const vector<int> &sample_stages = {8, 16, 32, 64, 128, 256})
477+
void renderPixelsSDLContinuous(vector<unsigned char> &image, int max_samples = 1024, int samples_per_batch = 8)
479478
{
480479
// Initialize SDL
481480
if (SDL_Init(SDL_INIT_VIDEO) < 0)
@@ -495,7 +494,7 @@ class Camera
495494
#endif
496495

497496
// Create window
498-
SDL_Window *window = SDL_CreateWindow("ISC - 302 ray tracer (mui) / Interactive mode (LMB:Rotate RMB:Pan Wheel:Zoom)",
497+
SDL_Window *window = SDL_CreateWindow("ISC - 302 ray tracer (mui) / Continuous mode (LMB:Rotate RMB:Pan Wheel:Zoom)",
499498
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
500499
image_width, image_height, SDL_WINDOW_SHOWN);
501500
if (!window)
@@ -535,54 +534,43 @@ class Camera
535534
unsigned char* logo_data = stbi_load("../res/ISC Logo inline white v3 - 1500px.png",
536535
&logo_img_width, &logo_img_height, &logo_img_channels, 4); // Force RGBA
537536

538-
SDL_Texture *logo_texture = nullptr;
539-
SDL_Rect logo_rect = {0, 0, 0, 0};
537+
SDL_Texture* logo_texture = nullptr;
538+
SDL_Rect logo_rect;
540539

541540
if (logo_data)
542541
{
543-
cout << "Logo loaded: " << logo_img_width << "x" << logo_img_height << " channels: " << logo_img_channels << endl;
542+
// Calculate logo dimensions (1/5 of window width, maintain aspect ratio)
543+
int target_logo_width = image_width / 5;
544+
int target_logo_height = (logo_img_height * target_logo_width) / logo_img_width;
544545

545-
// Calculate target logo size (1/10th of window width, maintain aspect ratio)
546-
int logo_width = image_width / 5;
547-
int logo_height = (logo_width * logo_img_height) / logo_img_width;
548-
549-
// Pre-scale logo using high-quality Mitchell filter for best quality
550-
unsigned char* scaled_logo = new unsigned char[logo_width * logo_height * 4];
546+
// Resize logo using high-quality Mitchell filter
547+
unsigned char* resized_logo = new unsigned char[target_logo_width * target_logo_height * 4];
551548
stbir_resize_uint8_srgb(logo_data, logo_img_width, logo_img_height, 0,
552-
scaled_logo, logo_width, logo_height, 0,
549+
resized_logo, target_logo_width, target_logo_height, 0,
553550
STBIR_RGBA);
554551

555-
// Create surface from pre-scaled logo data
556-
SDL_Surface *logo_surface = SDL_CreateRGBSurfaceFrom(
557-
scaled_logo, logo_width, logo_height, 32, logo_width * 4,
552+
// Create SDL surface from resized logo data
553+
SDL_Surface* logo_surface = SDL_CreateRGBSurfaceFrom(
554+
resized_logo, target_logo_width, target_logo_height, 32, target_logo_width * 4,
558555
0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000);
559556

560557
if (logo_surface)
561558
{
562559
logo_texture = SDL_CreateTextureFromSurface(renderer, logo_surface);
563-
SDL_FreeSurface(logo_surface);
560+
SDL_SetTextureBlendMode(logo_texture, SDL_BLENDMODE_BLEND);
564561

565-
if (logo_texture)
566-
{
567-
// Enable alpha blending for logo
568-
SDL_SetTextureBlendMode(logo_texture, SDL_BLENDMODE_BLEND);
569-
570-
// Position at bottom-right corner
571-
logo_rect.x = image_width - logo_width - 10; // 10px padding from edge
572-
logo_rect.y = image_height - logo_height - 10;
573-
logo_rect.w = logo_width;
574-
logo_rect.h = logo_height;
575-
}
562+
// Position logo in bottom-right corner with 10px padding
563+
logo_rect.w = target_logo_width;
564+
logo_rect.h = target_logo_height;
565+
logo_rect.x = image_width - target_logo_width - 10;
566+
logo_rect.y = image_height - target_logo_height - 10;
567+
568+
SDL_FreeSurface(logo_surface);
576569
}
577570

578-
// Clean up
579-
delete[] scaled_logo;
571+
delete[] resized_logo;
580572
stbi_image_free(logo_data);
581573
}
582-
else
583-
{
584-
cerr << "Warning: Could not load logo: " << stbi_failure_reason() << endl;
585-
}
586574

587575
// Load font for text rendering (using system mono font or DejaVu Sans Mono)
588576
void* font = nullptr;
@@ -597,45 +585,33 @@ class Camera
597585
}
598586
#endif
599587

600-
cout << "\n=== Interactive Progressive SDL Rendering ===" << endl;
588+
cout << "\n=== Interactive Ray Tracing with Real-time Display ===" << endl;
601589
cout << "Controls:" << endl;
602590
cout << " Left Mouse Button: Rotate camera (orbit)" << endl;
603591
cout << " Right Mouse Button: Pan camera" << endl;
604592
cout << " Mouse Wheel: Zoom in/out" << endl;
605593
cout << " ESC: Exit" << endl;
606-
cout << "Progressive quality stages: 8 → 16 → 32 → 64 → 128 → 256 samples" << endl;
607-
cout << "Full-frame rendering for 8 & 16 samples, 8x8 tiles for higher quality" << endl;
594+
cout << "Sample accumulation: " << samples_per_batch << " samples per batch, up to " << max_samples << " total samples" << endl;
595+
cout << "Using CUDA GPU acceleration with progressive refinement" << endl;
608596

609597
bool running = true;
610598
bool camera_changed = true; // Trigger initial render
611-
bool rendering = false; // Track if currently rendering
612-
size_t current_stage = 0; // Track current rendering stage
599+
int current_samples = 0; // Current accumulated sample count
613600
SDL_Event event;
614-
vector<unsigned char> stage_image(image_width * image_height * image_channels);
601+
vector<unsigned char> display_image(image_width * image_height * image_channels);
602+
vector<float> accum_buffer(image_width * image_height * image_channels, 0.0f); // Accumulation buffer for CUDA
603+
void *d_rand_states = nullptr; // Persistent device random states
615604

616-
// Initialize camera control state (now member variables)
605+
// Initialize camera control state
617606
left_button_down = false;
618607
right_button_down = false;
619608
last_mouse_x = 0;
620609
last_mouse_y = 0;
621610

622-
// Find starting stage (first stage with >= 8 samples for quick preview)
623-
size_t quick_start_stage = 0;
624-
for (size_t i = 0; i < sample_stages.size(); i++)
625-
{
626-
if (sample_stages[i] >= 8)
627-
{
628-
quick_start_stage = i;
629-
break;
630-
}
631-
}
632-
633-
cout << "Quick start stage index: " << quick_start_stage << " (samples: " << sample_stages[quick_start_stage] << ")" << endl;
634-
635611
// Initialize camera orbit parameters
636612
camera_distance = (lookfrom - lookat).length();
637-
camera_azimuth = 0.0; // Horizontal rotation angle
638-
camera_elevation = 0.0; // Vertical rotation angle (now member variable)
613+
camera_azimuth = 0.0;
614+
camera_elevation = 0.0;
639615

640616
// Calculate initial angles from current camera position
641617
Vec3 to_camera = lookfrom - lookat;
@@ -699,28 +675,23 @@ class Camera
699675
if (camera_elevation > max_elevation) camera_elevation = max_elevation;
700676
if (camera_elevation < -max_elevation) camera_elevation = -max_elevation;
701677

702-
// Calculate new camera position
703-
double cos_elev = cos(camera_elevation);
704-
lookfrom.e[0] = lookat.x() + camera_distance * cos_elev * sin(camera_azimuth);
678+
// Update camera position
679+
lookfrom.e[0] = lookat.x() + camera_distance * cos(camera_elevation) * sin(camera_azimuth);
705680
lookfrom.e[1] = lookat.y() + camera_distance * sin(camera_elevation);
706-
lookfrom.e[2] = lookat.z() + camera_distance * cos_elev * cos(camera_azimuth);
681+
lookfrom.e[2] = lookat.z() + camera_distance * cos(camera_elevation) * cos(camera_azimuth);
707682

708-
// Trigger re-render immediately
709683
camera_changed = true;
710684
}
711685
else if (right_button_down)
712686
{
713-
// Pan camera (move both lookfrom and lookat)
714-
Vec3 right = unit_vector(cross(lookfrom - lookat, vup));
715-
Vec3 up = unit_vector(cross(right, lookfrom - lookat));
716-
687+
// Pan camera (move lookat point)
717688
double pan_speed = camera_distance * 0.001;
718-
Vec3 pan_offset = right * (-dx * pan_speed) + up * (dy * pan_speed);
689+
Vec3 right = unit_vector(cross(vup, w)) * (-dx * pan_speed);
690+
Vec3 up = unit_vector(vup) * (dy * pan_speed);
719691

720-
lookfrom = lookfrom + pan_offset;
721-
lookat = lookat + pan_offset;
692+
lookat += right + up;
693+
lookfrom += right + up;
722694

723-
// Trigger re-render immediately
724695
camera_changed = true;
725696
}
726697

@@ -729,97 +700,100 @@ class Camera
729700
}
730701
else if (event.type == SDL_MOUSEWHEEL)
731702
{
732-
// Zoom in/out (change camera distance)
733-
double zoom_factor = event.wheel.y > 0 ? 0.9 : 1.1;
703+
// Zoom in/out
704+
double zoom_factor = (event.wheel.y > 0) ? 0.9 : 1.1;
734705
camera_distance *= zoom_factor;
735706

736-
// Recalculate camera position maintaining angles
737-
double cos_elev = cos(camera_elevation);
738-
lookfrom.e[0] = lookat.x() + camera_distance * cos_elev * sin(camera_azimuth);
707+
// Update camera position
708+
lookfrom.e[0] = lookat.x() + camera_distance * cos(camera_elevation) * sin(camera_azimuth);
739709
lookfrom.e[1] = lookat.y() + camera_distance * sin(camera_elevation);
740-
lookfrom.e[2] = lookat.z() + camera_distance * cos_elev * cos(camera_azimuth);
710+
lookfrom.e[2] = lookat.z() + camera_distance * cos(camera_elevation) * cos(camera_azimuth);
741711

742-
// Trigger re-render immediately
743712
camera_changed = true;
744713
}
745714
}
746-
747-
// Render if camera has changed
715+
716+
// If camera changed, restart rendering from scratch
748717
if (camera_changed)
749718
{
750719
camera_changed = false;
751-
rendering = true;
752-
initialize(); // Recalculate camera parameters
720+
current_samples = 0;
721+
std::fill(accum_buffer.begin(), accum_buffer.end(), 0.0f); // Clear accumulation buffer
753722

754-
// Start from quick preview stage when camera moves
755-
current_stage = quick_start_stage;
756-
cout << "\n[Camera changed - restarting from stage " << (current_stage + 1)
757-
<< " (index " << current_stage << ", samples: " << sample_stages[current_stage] << ")]" << endl;
723+
// Free and reset device random states to force reinitialization
724+
if (d_rand_states != nullptr) {
725+
freeDeviceRandomStates(d_rand_states);
726+
d_rand_states = nullptr;
727+
}
728+
729+
initialize(); // Recalculate camera parameters
730+
cout << "\n[Camera moved - restarting render]" << endl;
758731
}
759732

760-
// Continue progressive rendering through stages
761-
if (rendering && current_stage < sample_stages.size() && !camera_changed && running)
733+
// Continue adding samples if we haven't reached max
734+
if (current_samples < max_samples && !camera_changed && running)
762735
{
763-
int current_samples = sample_stages[current_stage];
764-
int original_samples = samples_per_pixel;
765-
samples_per_pixel = current_samples;
766-
767-
// For 8 and 16 samples, use full-frame rendering (1x1 tile = whole image)
768-
// For higher samples, use 8x8 tiles for responsiveness
769-
int tiles_per_axis = (current_samples <= 16) ? 1 : 8;
736+
// Add samples_per_batch new samples to the accumulation
737+
current_samples += samples_per_batch;
738+
if (current_samples > max_samples)
739+
current_samples = max_samples;
770740

771-
const char* render_mode = (tiles_per_axis == 1) ? "full frame" : "8x8 tiles";
772-
cout << "Rendering stage " << (current_stage - quick_start_stage + 1)
773-
<< " with " << current_samples << " samples ("
774-
<< render_mode << ")..." << flush;
775-
776-
// Render with tiled CUDA rendering for responsiveness
777-
bool completed = renderTiled(stage_image, tiles_per_axis, tiles_per_axis, renderer, texture,
778-
logo_texture, logo_rect, font, running, camera_changed);
779-
780-
if (completed)
781-
{
782-
cout << " done" << endl;
783-
784-
// Copy to final image buffer
785-
image = stage_image;
741+
int actual_samples_to_add = samples_per_batch;
742+
if (current_samples > max_samples)
743+
actual_samples_to_add = max_samples - (current_samples - samples_per_batch);
744+
745+
cout << "Rendering: " << current_samples << " samples..." << flush;
746+
747+
// Call CUDA accumulative rendering with persistent random states
748+
unsigned long long cuda_ray_count = ::renderPixelsCUDAAccumulative(
749+
display_image.data(), accum_buffer.data(),
750+
image_width, image_height,
751+
camera_center.x(), camera_center.y(), camera_center.z(),
752+
pixel00_loc.x(), pixel00_loc.y(), pixel00_loc.z(),
753+
pixel_delta_u.x(), pixel_delta_u.y(), pixel_delta_u.z(),
754+
pixel_delta_v.x(), pixel_delta_v.y(), pixel_delta_v.z(),
755+
actual_samples_to_add, current_samples, max_depth,
756+
&d_rand_states); // Pass persistent random states
757+
758+
n_rays.fetch_add(cuda_ray_count, std::memory_order_relaxed);
786759

787-
samples_per_pixel = original_samples;
788-
789-
// Move to next stage for next iteration
790-
current_stage++;
791-
792-
// Add 500ms delay after displaying each stage so user can see the quality improvement
793-
if (current_stage < sample_stages.size())
794-
{
795-
//cout << "delay !" << endl;
796-
//SDL_Delay(1500);
797-
}
798-
}
799-
else
800-
{
801-
cout << " interrupted" << endl;
802-
samples_per_pixel = original_samples;
803-
// camera_changed or running will be handled in the next loop iteration
804-
}
760+
// Update display
761+
SDL_UpdateTexture(texture, nullptr, display_image.data(), image_width * image_channels);
762+
SDL_RenderClear(renderer);
763+
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
805764

806-
// If we've completed all stages, stop rendering
807-
if (current_stage >= sample_stages.size())
765+
// Draw logo if available
766+
if (logo_texture)
808767
{
809-
rendering = false;
768+
SDL_RenderCopy(renderer, logo_texture, nullptr, &logo_rect);
810769
}
770+
771+
// Draw sample count text
772+
drawSampleCountText(renderer, font, current_samples);
773+
774+
SDL_RenderPresent(renderer);
775+
776+
// Copy to final image buffer
777+
image = display_image;
778+
779+
cout << " done" << endl;
811780
}
812-
813-
// Small delay when idle to avoid busy-waiting
814-
if (!rendering && !camera_changed)
781+
else if (current_samples >= max_samples && !camera_changed)
815782
{
783+
// Rendering complete, just wait for events
816784
SDL_Delay(16); // ~60 FPS event polling
817785
}
818786
}
819787

820788
auto total_end = std::chrono::high_resolution_clock::now();
821789
cout << "\nTotal session time: " << timeStr(total_end - total_start) << endl;
822790

791+
// Cleanup device random states
792+
if (d_rand_states != nullptr) {
793+
freeDeviceRandomStates(d_rand_states);
794+
d_rand_states = nullptr;
795+
}
796+
823797
// Cleanup
824798
#ifdef SDL2_TTF_FOUND
825799
if (font)
@@ -838,6 +812,7 @@ class Camera
838812
SDL_DestroyWindow(window);
839813
SDL_Quit();
840814
}
815+
841816
#endif // SDL2_FOUND
842817

843818
private:

0 commit comments

Comments
 (0)