Skip to content

Commit cd710bb

Browse files
committed
Add progressive SDL2 rendering with real-time quality improvements
- Implement renderPixelsSDLProgressive() method in camera.h - Display rendering progress through sample stages: 1, 4, 16, 64, 256, 1024 - Add interactive SDL window with live image updates - Support ESC key and window close to stop at current quality - Add rendering method option 3 for progressive display - Include SDL2 headers conditionally when available - Add comprehensive documentation in PROGRESSIVE_SDL_RENDERING.md - Feature allows quick preview and interactive quality control
1 parent f518a3c commit cd710bb

File tree

3 files changed

+343
-1
lines changed

3 files changed

+343
-1
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Progressive SDL Rendering Feature
2+
3+
## Overview
4+
5+
This branch adds an interactive SDL2-based progressive rendering mode to the raytracer. This feature allows you to see your render in real-time as it progressively improves in quality through multiple sample stages.
6+
7+
## What is Progressive Rendering?
8+
9+
Progressive rendering displays the image while it's being rendered, starting with low quality (few samples per pixel) and gradually increasing quality. This allows you to:
10+
11+
- **Preview quickly**: See your scene in seconds with low sample counts
12+
- **Stop early**: If the preview looks wrong, you can stop and adjust parameters
13+
- **Watch progress**: See the image quality improve in real-time
14+
- **Interactive experience**: Get visual feedback during the rendering process
15+
16+
## Features
17+
18+
### Sample Stages
19+
20+
The progressive rendering goes through multiple quality stages:
21+
- **Stage 1**: 1 sample per pixel (very fast, noisy preview)
22+
- **Stage 2**: 4 samples per pixel (quick preview with less noise)
23+
- **Stage 3**: 16 samples per pixel (decent quality)
24+
- **Stage 4**: 64 samples per pixel (good quality)
25+
- **Stage 5**: 256 samples per pixel (high quality)
26+
- **Stage 6**: 1024 samples per pixel (final quality)
27+
28+
Each stage is displayed immediately in the SDL window, so you can see the quality progression.
29+
30+
### Interactive Controls
31+
32+
- **ESC key**: Stop rendering at the current quality level
33+
- **Close window**: Stop rendering at the current quality level
34+
- **Wait**: Let it complete all stages for the highest quality
35+
- **After completion**: Press any key or close window to save and continue
36+
37+
## How to Use
38+
39+
### Prerequisites
40+
41+
Make sure SDL2 is installed on your system:
42+
43+
```bash
44+
# Ubuntu/Debian
45+
sudo apt-get install libsdl2-dev
46+
47+
# macOS
48+
brew install sdl2
49+
50+
# Windows
51+
# Download from https://www.libsdl.org/download-2.0.php
52+
```
53+
54+
### Building
55+
56+
The feature is automatically compiled if SDL2 is detected:
57+
58+
```bash
59+
cd build
60+
cmake .. --fresh
61+
make -j12
62+
```
63+
64+
If SDL2 is found, you'll see:
65+
```
66+
-- SDL2 found and linked for real-time display
67+
```
68+
69+
### Running
70+
71+
When you run the raytracer, you'll see a new rendering option:
72+
73+
```bash
74+
./302_raytracer -r 1080 -s 1024
75+
```
76+
77+
The menu will show:
78+
```
79+
Choose rendering method:
80+
0. CPU sequential
81+
1. CPU parallel
82+
2. CUDA GPU (default)
83+
3. CUDA GPU with progressive SDL display
84+
Enter choice (0, 1, 2, or 3):
85+
```
86+
87+
Select option **3** for progressive SDL rendering.
88+
89+
### Example Usage
90+
91+
```bash
92+
# Progressive render at 1080p
93+
echo 3 | ./302_raytracer -r 1080
94+
95+
# Progressive render at 4K
96+
echo 3 | ./302_raytracer -r 2160
97+
98+
# Progressive render at 720p with custom samples
99+
echo 3 | ./302_raytracer -r 720 -s 512
100+
```
101+
102+
## Technical Details
103+
104+
### Implementation
105+
106+
The progressive rendering feature is implemented in `camera.h`:
107+
- **Method**: `renderPixelsSDLProgressive()`
108+
- **Conditional compilation**: Only available when `SDL2_FOUND` is defined
109+
- **Backend**: Uses CUDA for each rendering stage (fast GPU rendering)
110+
- **Display**: Updates SDL texture after each stage completion
111+
112+
### Rendering Flow
113+
114+
1. Initialize SDL window, renderer, and texture
115+
2. For each sample stage:
116+
- Render with CUDA at current sample count
117+
- Update SDL texture with the new image data
118+
- Display in the window
119+
- Check for user interruption (ESC or window close)
120+
3. Wait for user to acknowledge completion
121+
4. Clean up SDL resources
122+
5. Return final image for saving
123+
124+
### Customization
125+
126+
You can customize the sample stages in `main.cc`:
127+
128+
```cpp
129+
// Default stages
130+
c.renderPixelsSDLProgressive(localImage, {1, 4, 16, 64, 256, 1024});
131+
132+
// Custom stages - more granular
133+
c.renderPixelsSDLProgressive(localImage, {1, 2, 4, 8, 16, 32, 64, 128, 256});
134+
135+
// Quick preview stages
136+
c.renderPixelsSDLProgressive(localImage, {1, 4, 16});
137+
```
138+
139+
## Performance Considerations
140+
141+
- **GPU Required**: Uses CUDA for rendering each stage, so a CUDA-capable GPU is required
142+
- **Memory**: Keeps one full-resolution image buffer per stage
143+
- **Display overhead**: Minimal - SDL texture updates are fast
144+
- **Total time**: Roughly equivalent to rendering with the highest sample count directly
145+
146+
## Benefits Over Standard Rendering
147+
148+
1. **Immediate feedback**: See your scene within seconds
149+
2. **Error detection**: Catch scene setup mistakes early
150+
3. **Quality control**: Stop when quality is sufficient for your needs
151+
4. **Visual satisfaction**: Watch the rendering improve in real-time
152+
5. **Flexibility**: Adjust quality vs. speed tradeoff interactively
153+
154+
## Troubleshooting
155+
156+
### SDL2 Not Found
157+
158+
If option 3 doesn't appear:
159+
- SDL2 is not installed or not found by CMake
160+
- Reinstall SDL2 and run `cmake .. --fresh` in the build directory
161+
162+
### Window Doesn't Appear
163+
164+
- Check that you're not in a headless environment
165+
- Verify SDL2 is properly linked: `ldd ./302_raytracer | grep SDL`
166+
167+
### Slow Performance
168+
169+
- Progressive rendering uses CUDA, ensure your GPU drivers are up to date
170+
- Each stage requires a complete render at that sample count
171+
- For slower GPUs, consider using fewer or lower sample stages
172+
173+
## Future Enhancements
174+
175+
Potential improvements for this feature:
176+
- Adjustable sample stages via command-line arguments
177+
- Real-time statistics overlay (FPS, rays/sec, current stage)
178+
- Pause/resume functionality
179+
- Save intermediate stages
180+
- Tile-based progressive rendering for huge resolutions
181+
- Multi-GPU support for faster stage completion
182+
183+
## Code References
184+
185+
- **Main implementation**: `src/302_raytracer/camera.h` - `renderPixelsSDLProgressive()`
186+
- **Menu integration**: `src/302_raytracer/main.cc` - rendering method selection
187+
- **CMake configuration**: `CMakeLists.txt` - SDL2 detection and linking
188+
- **CUDA backend**: `src/302_raytracer/camera_cuda.cu` - `renderPixelsCUDA()`

src/302_raytracer/camera.h

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
#include <thread>
2020
#include <vector>
2121

22+
#ifdef SDL2_FOUND
23+
#include <SDL.h>
24+
#endif
25+
2226
#pragma once
2327

2428
class Camera
@@ -187,6 +191,143 @@ class Camera
187191
cout << "CUDA rendering completed in " << timeStr(duration) << endl;
188192
}
189193

194+
#ifdef SDL2_FOUND
195+
/**
196+
* @brief Renders progressively with SDL2 window display showing incremental quality improvements
197+
*
198+
* This method displays the image in an SDL window while rendering with increasing sample rates.
199+
* It starts with low sample counts for quick preview and progressively increases quality.
200+
* User can close the window at any time or wait for the final quality render.
201+
*
202+
* @param image The final image buffer to store the highest quality render
203+
* @param sample_stages Vector of sample counts for progressive rendering (e.g., {1, 4, 16, 64, 256})
204+
*/
205+
void renderPixelsSDLProgressive(vector<unsigned char> &image, const vector<int> &sample_stages = {1, 4, 16, 64, 256})
206+
{
207+
// Initialize SDL
208+
if (SDL_Init(SDL_INIT_VIDEO) < 0)
209+
{
210+
cerr << "SDL initialization failed: " << SDL_GetError() << endl;
211+
return;
212+
}
213+
214+
// Create window
215+
SDL_Window *window = SDL_CreateWindow("Ray Tracer - Progressive Rendering", SDL_WINDOWPOS_CENTERED,
216+
SDL_WINDOWPOS_CENTERED, image_width, image_height, SDL_WINDOW_SHOWN);
217+
if (!window)
218+
{
219+
cerr << "Window creation failed: " << SDL_GetError() << endl;
220+
SDL_Quit();
221+
return;
222+
}
223+
224+
// Create renderer
225+
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
226+
if (!renderer)
227+
{
228+
cerr << "Renderer creation failed: " << SDL_GetError() << endl;
229+
SDL_DestroyWindow(window);
230+
SDL_Quit();
231+
return;
232+
}
233+
234+
// Create texture for displaying the image
235+
SDL_Texture *texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING,
236+
image_width, image_height);
237+
if (!texture)
238+
{
239+
cerr << "Texture creation failed: " << SDL_GetError() << endl;
240+
SDL_DestroyRenderer(renderer);
241+
SDL_DestroyWindow(window);
242+
SDL_Quit();
243+
return;
244+
}
245+
246+
cout << "\n=== Progressive SDL Rendering ===" << endl;
247+
cout << "Close window or press ESC to stop at current quality\n" << endl;
248+
249+
bool running = true;
250+
SDL_Event event;
251+
vector<unsigned char> stage_image(image_width * image_height * image_channels);
252+
253+
auto total_start = std::chrono::high_resolution_clock::now();
254+
255+
// Progressive rendering through sample stages
256+
for (size_t stage = 0; stage < sample_stages.size() && running; stage++)
257+
{
258+
int current_samples = sample_stages[stage];
259+
int original_samples = samples_per_pixel;
260+
samples_per_pixel = current_samples;
261+
262+
cout << "Rendering stage " << (stage + 1) << "/" << sample_stages.size() << " with " << current_samples
263+
<< " samples per pixel..." << flush;
264+
265+
auto stage_start = std::chrono::high_resolution_clock::now();
266+
267+
// Render with CUDA for this stage
268+
unsigned long long cuda_ray_count =
269+
::renderPixelsCUDA(stage_image.data(), image_width, image_height, camera_center.x(), camera_center.y(),
270+
camera_center.z(), pixel00_loc.x(), pixel00_loc.y(), pixel00_loc.z(), pixel_delta_u.x(),
271+
pixel_delta_u.y(), pixel_delta_u.z(), pixel_delta_v.x(), pixel_delta_v.y(),
272+
pixel_delta_v.z(), current_samples, max_depth);
273+
274+
n_rays.fetch_add(cuda_ray_count, std::memory_order_relaxed);
275+
276+
auto stage_end = std::chrono::high_resolution_clock::now();
277+
cout << " completed in " << timeStr(stage_end - stage_start) << endl;
278+
279+
// Update SDL texture and display
280+
SDL_UpdateTexture(texture, nullptr, stage_image.data(), image_width * image_channels);
281+
SDL_RenderClear(renderer);
282+
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
283+
SDL_RenderPresent(renderer);
284+
285+
// Copy to final image buffer
286+
image = stage_image;
287+
288+
// Check for SDL events (window close, ESC key, etc.)
289+
while (SDL_PollEvent(&event))
290+
{
291+
if (event.type == SDL_QUIT || (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE))
292+
{
293+
running = false;
294+
cout << "\nRendering stopped by user at stage " << (stage + 1) << "/" << sample_stages.size() << endl;
295+
break;
296+
}
297+
}
298+
299+
samples_per_pixel = original_samples;
300+
}
301+
302+
auto total_end = std::chrono::high_resolution_clock::now();
303+
cout << "\nTotal progressive rendering time: " << timeStr(total_end - total_start) << endl;
304+
305+
if (running)
306+
{
307+
cout << "Press any key or close window to continue..." << endl;
308+
// Wait for user to close window or press a key
309+
while (running)
310+
{
311+
while (SDL_PollEvent(&event))
312+
{
313+
if (event.type == SDL_QUIT || event.type == SDL_KEYDOWN)
314+
{
315+
running = false;
316+
break;
317+
}
318+
}
319+
SDL_Delay(100);
320+
}
321+
}
322+
323+
// Cleanup
324+
SDL_DestroyTexture(texture);
325+
SDL_DestroyRenderer(renderer);
326+
SDL_DestroyWindow(window);
327+
SDL_Quit();
328+
}
329+
#endif // SDL2_FOUND
330+
190331
private:
191332
Point3 camera_center; // Camera center
192333
Point3 pixel00_loc; // Location of pixel 0, 0

src/302_raytracer/main.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,14 @@ int main(int argc, char *argv[])
179179
cout << "\t0. CPU sequential" << endl;
180180
cout << "\t1. CPU parallel" << endl;
181181
cout << "\t2. CUDA GPU (default)" << endl;
182-
cout << "Enter choice (0, 1, or 2): ";
182+
#ifdef SDL2_FOUND
183+
cout << "\t3. CUDA GPU with progressive SDL display" << endl;
184+
#endif
185+
cout << "Enter choice (0, 1, 2"
186+
#ifdef SDL2_FOUND
187+
<< ", or 3"
188+
#endif
189+
<< "): ";
183190

184191
int choice = 2; // Default to CUDA
185192
string input;
@@ -202,6 +209,12 @@ int main(int argc, char *argv[])
202209
cout << "Using CPU parallel rendering..." << endl;
203210
c.renderPixelsParallel(scene, localImage);
204211
break;
212+
#ifdef SDL2_FOUND
213+
case 3:
214+
cout << "Using CUDA GPU with progressive SDL display..." << endl;
215+
c.renderPixelsSDLProgressive(localImage, {1, 4, 16, 64, 256, 1024});
216+
break;
217+
#endif
205218
default:
206219
cout << "Using CUDA GPU rendering..." << endl;
207220
c.renderPixelsCUDA(localImage);

0 commit comments

Comments
 (0)