@@ -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 << " \n Total 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