@@ -67,6 +67,10 @@ float4 PS(PS_IN input) : SV_Target
6767 private static double [ ] uploadMs ;
6868 private static double [ ] gpuMs ;
6969 private static double sceneBuildMs ;
70+ private static MainForm form ;
71+ private static bool resizePending ;
72+ private static bool screenshotRequested ;
73+ private static float cameraTime ;
7074
7175 [ STAThread ]
7276 private static void Main ( string [ ] args )
@@ -76,17 +80,26 @@ private static void Main(string[] args)
7680 screenshotPath = Path . Combine ( AppContext . BaseDirectory , ".." , ".." , ".." , "screenshot.png" ) ;
7781 screenshotPath = Path . GetFullPath ( screenshotPath ) ;
7882
79- var windowSystem = new FormsWindowsSystem ( ) ;
80- var window = windowSystem . CreateWindow ( "Embree.NET + Evergine low-level" , Width , Height ) ;
83+ System . Windows . Forms . Application . EnableVisualStyles ( ) ;
84+ System . Windows . Forms . Application . SetCompatibleTextRenderingDefault ( false ) ;
85+
86+ form = new MainForm ( ( int ) Width , ( int ) Height ) ;
87+ form . ScreenshotButton . Click += ( s , e ) => screenshotRequested = true ;
88+
89+ // The HWND must exist before the swapchain is built, and it must be read *after* the
90+ // control has been parented: WinForms recreates a control's handle when it is added to
91+ // a container, which would leave the swapchain bound to a dead window.
92+ form . CreateControl ( ) ;
93+ IntPtr renderHandle = form . RenderControl . Handle ;
8194
8295 graphicsContext = new DX11GraphicsContext ( ) ;
8396 graphicsContext . CreateDevice ( ) ;
8497
8598 var swapChainDescription = new SwapChainDescription ( )
8699 {
87- Width = window . Width ,
88- Height = window . Height ,
89- SurfaceInfo = window . SurfaceInfo ,
100+ Width = ( uint ) form . RenderControl . ClientSize . Width ,
101+ Height = ( uint ) form . RenderControl . ClientSize . Height ,
102+ SurfaceInfo = new SurfaceInfo ( renderHandle , SurfaceInfo . SurfaceTypes . Forms ) ,
90103 ColorTargetFormat = Evergine . Common . Graphics . PixelFormat . R8G8B8A8_UNorm ,
91104 ColorTargetFlags = TextureFlags . RenderTarget | TextureFlags . ShaderResource ,
92105 DepthStencilTargetFormat = Evergine . Common . Graphics . PixelFormat . D24_UNorm_S8_UInt ,
@@ -101,9 +114,32 @@ private static void Main(string[] args)
101114 // VSync would clamp the measured frame time to the display refresh rate.
102115 swapChain . VerticalSync = ! benchmark ;
103116
117+ form . RenderControl . ClientSizeChanged += ( s , e ) => resizePending = true ;
118+
119+ // Drive the render loop from the form itself, so it ends when the window is closed.
120+ var windowSystem = new FormsWindowsSystem { AutoRegisterWindow = false } ;
121+ windowSystem . RegisterLoopThreadControl ( form ) ;
104122 windowSystem . Run ( Load , Draw ) ;
105123 }
106124
125+ /// <summary>
126+ /// Matches the swapchain to the current size of the hosting control. The ray traced image
127+ /// keeps its own fixed resolution and is stretched by the fullscreen triangle, so resizing
128+ /// costs nothing on the CPU side.
129+ /// </summary>
130+ private static void ApplyPendingResize ( )
131+ {
132+ resizePending = false ;
133+
134+ uint width = ( uint ) Math . Max ( form . RenderControl . ClientSize . Width , 1 ) ;
135+ uint height = ( uint ) Math . Max ( form . RenderControl . ClientSize . Height , 1 ) ;
136+
137+ swapChain . ResizeSwapChain ( width , height ) ;
138+
139+ viewports [ 0 ] = new Viewport ( 0 , 0 , width , height ) ;
140+ scissors [ 0 ] = new Evergine . Mathematics . Rectangle ( 0 , 0 , ( int ) width , ( int ) height ) ;
141+ }
142+
107143 private static void Load ( )
108144 {
109145 long buildStart = Stopwatch . GetTimestamp ( ) ;
@@ -134,7 +170,8 @@ private static void Load()
134170 } ;
135171 rayTexture = graphicsContext . Factory . CreateTexture ( ref textureDescription ) ;
136172
137- var samplerDescription = SamplerStates . PointClamp ;
173+ // Linear so the fixed-resolution ray traced image scales cleanly when the window is resized.
174+ var samplerDescription = SamplerStates . LinearClamp ;
138175 var sampler = graphicsContext . Factory . CreateSamplerState ( ref samplerDescription ) ;
139176
140177 var vertexShaderDescription = new ShaderDescription (
@@ -176,15 +213,29 @@ private static void Load()
176213
177214 viewports = new [ ] { new Viewport ( 0 , 0 , Width , Height ) } ;
178215 scissors = new [ ] { new Evergine . Mathematics . Rectangle ( 0 , 0 , ( int ) Width , ( int ) Height ) } ;
216+
217+ form . SetSceneInfo ( raytracer . TriangleCount , raytracer . GeometryCount , ( int ) Width , ( int ) Height ) ;
218+ ApplyPendingResize ( ) ;
179219 }
180220
181221 private static void Draw ( )
182222 {
223+ if ( resizePending )
224+ {
225+ ApplyPendingResize ( ) ;
226+ }
227+
183228 swapChain . InitFrame ( ) ;
184229
230+ // The camera is frozen while benchmarking so every frame traces the same image.
231+ if ( ! benchmark && ! form . AnimateButton . Checked )
232+ {
233+ cameraTime = ( float ) clock . Elapsed . TotalSeconds ;
234+ }
235+
185236 // 1. CPU ray tracing with Embree.
186237 long t0 = Stopwatch . GetTimestamp ( ) ;
187- raytracer . Render ( pixels , ( int ) Width , ( int ) Height , benchmark ? 0.0f : ( float ) clock . Elapsed . TotalSeconds ) ;
238+ raytracer . Render ( pixels , ( int ) Width , ( int ) Height , cameraTime ) ;
188239 long t1 = Stopwatch . GetTimestamp ( ) ;
189240
190241 // 2. Upload to the GPU texture.
@@ -234,8 +285,11 @@ private static void Draw()
234285 return ;
235286 }
236287
237- if ( frameIndex == ScreenshotFrame )
288+ form . SetTimings ( ToMilliseconds ( t1 - t0 ) , ToMilliseconds ( t2 - t1 ) , ToMilliseconds ( t3 - t2 ) ) ;
289+
290+ if ( frameIndex == ScreenshotFrame || screenshotRequested )
238291 {
292+ screenshotRequested = false ;
239293 SaveScreenshot ( ) ;
240294
241295 if ( exitAfterScreenshot )
@@ -305,6 +359,10 @@ private static unsafe void SaveScreenshot()
305359 {
306360 Texture source = swapChain . FrameBuffer . ColorTargets [ 0 ] . Texture ;
307361
362+ // The swapchain follows the control size, which is not the ray tracing resolution.
363+ int width = ( int ) source . Description . Width ;
364+ int height = ( int ) source . Description . Height ;
365+
308366 var stagingDescription = source . Description ;
309367 stagingDescription . Flags = TextureFlags . None ;
310368 stagingDescription . CpuAccess = ResourceCpuAccess . Read ;
@@ -323,20 +381,20 @@ private static unsafe void SaveScreenshot()
323381
324382 try
325383 {
326- using var bitmap = new System . Drawing . Bitmap ( ( int ) Width , ( int ) Height , System . Drawing . Imaging . PixelFormat . Format32bppArgb ) ;
384+ using var bitmap = new System . Drawing . Bitmap ( width , height , System . Drawing . Imaging . PixelFormat . Format32bppArgb ) ;
327385 var bitmapData = bitmap . LockBits (
328- new System . Drawing . Rectangle ( 0 , 0 , ( int ) Width , ( int ) Height ) ,
386+ new System . Drawing . Rectangle ( 0 , 0 , width , height ) ,
329387 System . Drawing . Imaging . ImageLockMode . WriteOnly ,
330388 System . Drawing . Imaging . PixelFormat . Format32bppArgb ) ;
331389
332390 try
333391 {
334- for ( int y = 0 ; y < Height ; y ++ )
392+ for ( int y = 0 ; y < height ; y ++ )
335393 {
336394 byte * sourceRow = ( byte * ) mapped . Data + ( y * mapped . RowPitch ) ;
337395 byte * destinationRow = ( byte * ) bitmapData . Scan0 + ( y * bitmapData . Stride ) ;
338396
339- for ( int x = 0 ; x < Width ; x ++ )
397+ for ( int x = 0 ; x < width ; x ++ )
340398 {
341399 // Swapchain is RGBA, GDI+ expects BGRA.
342400 destinationRow [ ( x * 4 ) + 0 ] = sourceRow [ ( x * 4 ) + 2 ] ;
0 commit comments