@@ -55,6 +55,18 @@ namespace
5555{
5656constexpr const char * kDefaultDismissFile = " /tmp/.dismissSplash" ;
5757
58+ // The splash is static: redraw only when needed (first frame, display-size change).
59+ // Poll the dismiss file on a short timer to remain responsive without busy-spinning.
60+ constexpr uint64_t kDismissPollIntervalMs = 50 ;
61+
62+ // Some platforms/compositors expect periodic presents; redraw/present at ~20Hz to
63+ // avoid flicker while keeping CPU/GPU usage low.
64+ constexpr uint64_t kRedrawIntervalMs = 50 ;
65+
66+ // Pump the Essos event loop more frequently than we redraw to keep IPC/input
67+ // latency low (e.g. compositor/display messages).
68+ constexpr useconds_t kEventLoopSleepUs = 10'000 ;
69+
5870volatile std::sig_atomic_t gShouldQuit = 0 ;
5971
6072uint64_t nowMs ()
@@ -156,19 +168,17 @@ std::optional<Image> decodeJpegToRgba(const std::string& path)
156168 const int y = static_cast <int >(cinfo.output_scanline - 1 );
157169 uint8_t * dst = out.rgba .data () + (static_cast <size_t >(y) * static_cast <size_t >(width) * 4 );
158170
159- if (components == 3 )
171+ for ( int x = 0 ; x < width; ++x )
160172 {
161- for (int x = 0 ; x < width; ++x)
162- {
163- const uint8_t r = row[static_cast <size_t >(x) * 3 + 0 ];
164- const uint8_t g = row[static_cast <size_t >(x) * 3 + 1 ];
165- const uint8_t b = row[static_cast <size_t >(x) * 3 + 2 ];
166- dst[static_cast <size_t >(x) * 4 + 0 ] = r;
167- dst[static_cast <size_t >(x) * 4 + 1 ] = g;
168- dst[static_cast <size_t >(x) * 4 + 2 ] = b;
169- dst[static_cast <size_t >(x) * 4 + 3 ] = 0xFF ;
170- }
173+ const uint8_t r = row[static_cast <size_t >(x) * 3 + 0 ];
174+ const uint8_t g = row[static_cast <size_t >(x) * 3 + 1 ];
175+ const uint8_t b = row[static_cast <size_t >(x) * 3 + 2 ];
176+ dst[static_cast <size_t >(x) * 4 + 0 ] = r;
177+ dst[static_cast <size_t >(x) * 4 + 1 ] = g;
178+ dst[static_cast <size_t >(x) * 4 + 2 ] = b;
179+ dst[static_cast <size_t >(x) * 4 + 3 ] = 0xFF ;
171180 }
181+
172182 }
173183
174184 jpeg_finish_decompress (&cinfo);
@@ -243,6 +253,9 @@ std::optional<Image> decodePngToRgba(const std::string& path)
243253 int colorType = 0 ;
244254 png_get_IHDR (pngPtr, infoPtr, &width, &height, &bitDepth, &colorType, nullptr , nullptr , nullptr );
245255
256+ const bool fileHasAlpha = (colorType & PNG_COLOR_MASK_ALPHA ) != 0 ;
257+ const bool fileHasTrns = png_get_valid (pngPtr, infoPtr, PNG_INFO_tRNS) != 0 ;
258+
246259 if (bitDepth == 16 )
247260 png_set_strip_16 (pngPtr);
248261
@@ -252,18 +265,32 @@ std::optional<Image> decodePngToRgba(const std::string& path)
252265 if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8 )
253266 png_set_expand_gray_1_2_4_to_8 (pngPtr);
254267
255- if (png_get_valid (pngPtr, infoPtr, PNG_INFO_tRNS) )
268+ if (fileHasTrns )
256269 png_set_tRNS_to_alpha (pngPtr);
257270
258271 if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA )
259272 png_set_gray_to_rgb (pngPtr);
260273
261- // Ensure RGBA.
262- if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE )
274+ // Ensure RGBA output. After our transforms, the output will have an alpha
275+ // channel iff the file already had one OR we expanded tRNS.
276+ const bool outputHasAlpha = fileHasAlpha || fileHasTrns;
277+ if (!outputHasAlpha)
263278 png_set_filler (pngPtr, 0xFF , PNG_FILLER_AFTER );
264279
265280 png_read_update_info (pngPtr, infoPtr);
266281
282+ // Re-query post-transform state for clarity/robustness.
283+ const int outColorType = png_get_color_type (pngPtr, infoPtr);
284+ const int outBitDepth = png_get_bit_depth (pngPtr, infoPtr);
285+ if (outColorType != PNG_COLOR_TYPE_RGBA || outBitDepth != 8 )
286+ {
287+ std::printf (" decodePng: unsupported output format after transforms: colorType=%d bitDepth=%d\n " ,
288+ outColorType, outBitDepth);
289+ png_destroy_read_struct (&pngPtr, &infoPtr, nullptr );
290+ std::fclose (fp);
291+ return std::nullopt ;
292+ }
293+
267294 const png_size_t rowBytes = png_get_rowbytes (pngPtr, infoPtr);
268295 if (rowBytes != width * 4 )
269296 {
@@ -694,60 +721,98 @@ int main(int argc, char** argv)
694721 glEnableVertexAttribArray (1 );
695722
696723 bool firstFrame = true ;
724+ bool warnedNoDisplaySize = false ;
725+ int lastDrawDisplayWidth = -1 ;
726+ int lastDrawDisplayHeight = -1 ;
727+ uint64_t lastDrawMs = 0 ;
728+ uint64_t lastDismissCheckMs = 0 ;
697729 while (!gShouldQuit )
698730 {
699- if (fileExists (opt->dismissFile .c_str ()))
700- break ;
731+ const uint64_t loopNowMs = nowMs ();
732+ if (lastDismissCheckMs == 0 || (loopNowMs - lastDismissCheckMs) >= kDismissPollIntervalMs )
733+ {
734+ lastDismissCheckMs = loopNowMs;
735+ if (fileExists (opt->dismissFile .c_str ()))
736+ break ;
737+ }
701738
702- // Preserve aspect ratio by fitting the image into the display with
703- // letterboxing/pillarboxing.
704- GLfloat scaleX = 1 .0f ;
705- GLfloat scaleY = 1 .0f ;
706- if (display.width > 0 && display.height > 0 && image->width > 0 && image->height > 0 )
739+ if (display.width <= 0 || display.height <= 0 )
707740 {
708- const double displayAspect = static_cast <double >(display.width ) / static_cast <double >(display.height );
709- const double imageAspect = static_cast <double >(image->width ) / static_cast <double >(image->height );
710- if (imageAspect > displayAspect)
741+ if (!warnedNoDisplaySize)
711742 {
712- scaleY = static_cast <GLfloat>(displayAspect / imageAspect);
743+ warnedNoDisplaySize = true ;
744+ std::printf (" Display size not available yet (got %dx%d); waiting for settings update...\n " ,
745+ display.width , display.height );
713746 }
714- else
747+
748+ EssContextRunEventLoopOnce (ctx);
749+ usleep (kEventLoopSleepUs );
750+ continue ;
751+ }
752+
753+ const bool displayChanged = (display.width != lastDrawDisplayWidth) || (display.height != lastDrawDisplayHeight);
754+ const bool dueToRedraw = (lastDrawMs == 0 ) || ((loopNowMs - lastDrawMs) >= kRedrawIntervalMs );
755+ const bool shouldDraw = firstFrame || displayChanged || dueToRedraw;
756+
757+ if (shouldDraw)
758+ {
759+ // Re-assert key GL state before drawing in case the platform touches it.
760+ glUseProgram (gl.program );
761+ glActiveTexture (GL_TEXTURE0 );
762+ glBindTexture (GL_TEXTURE_2D , gl.texture );
763+
764+ // Preserve aspect ratio by fitting the image into the display with
765+ // letterboxing/pillarboxing.
766+ GLfloat scaleX = 1 .0f ;
767+ GLfloat scaleY = 1 .0f ;
768+ if (display.width > 0 && display.height > 0 && image->width > 0 && image->height > 0 )
715769 {
716- scaleX = static_cast <GLfloat>(imageAspect / displayAspect);
770+ const double displayAspect = static_cast <double >(display.width ) / static_cast <double >(display.height );
771+ const double imageAspect = static_cast <double >(image->width ) / static_cast <double >(image->height );
772+ if (imageAspect > displayAspect)
773+ {
774+ scaleY = static_cast <GLfloat>(displayAspect / imageAspect);
775+ }
776+ else
777+ {
778+ scaleX = static_cast <GLfloat>(imageAspect / displayAspect);
779+ }
717780 }
718- }
719781
720- const GLfloat verts[4 ][2 ] = {
721- {-scaleX, -scaleY},
722- {scaleX, -scaleY},
723- {-scaleX, scaleY},
724- {scaleX, scaleY},
725- };
782+ const GLfloat verts[4 ][2 ] = {
783+ {-scaleX, -scaleY},
784+ {scaleX, -scaleY},
785+ {-scaleX, scaleY},
786+ {scaleX, scaleY},
787+ };
726788
727- glVertexAttribPointer (0 , 2 , GL_FLOAT , GL_FALSE , 0 , verts);
789+ glVertexAttribPointer (0 , 2 , GL_FLOAT , GL_FALSE , 0 , verts);
728790
729- glViewport (0 , 0 , display.width , display.height );
730- glClear (GL_COLOR_BUFFER_BIT );
731- glDrawArrays (GL_TRIANGLE_STRIP , 0 , 4 );
791+ glViewport (0 , 0 , display.width , display.height );
792+ glClear (GL_COLOR_BUFFER_BIT );
793+ glDrawArrays (GL_TRIANGLE_STRIP , 0 , 4 );
732794
733- if (firstFrame)
734- {
735- firstFrame = false ;
736- char msg[256 ];
737- std::snprintf (msg, sizeof (msg),
738- " RDKE_Splash first_frame_ms=%llu decode_ms=%llu gl_setup_ms=%llu img=%dx%d" ,
739- static_cast <unsigned long long >(nowMs () - startMs),
740- static_cast <unsigned long long >(decodeMs),
741- static_cast <unsigned long long >(glSetupMs),
742- image->width , image->height );
743- std::printf (" %s\n " , msg);
795+ // Present only after drawing a frame.
796+ EssContextUpdateDisplay (ctx);
797+ glFlush ();
798+
799+ lastDrawMs = loopNowMs;
800+ lastDrawDisplayWidth = display.width ;
801+ lastDrawDisplayHeight = display.height ;
802+
803+ if (firstFrame)
804+ {
805+ firstFrame = false ;
806+ std::printf (" RDKE_Splash first_frame_ms=%llu decode_ms=%llu gl_setup_ms=%llu img=%dx%d\n " ,
807+ static_cast <unsigned long long >(nowMs () - startMs),
808+ static_cast <unsigned long long >(decodeMs),
809+ static_cast <unsigned long long >(glSetupMs),
810+ image->width , image->height );
811+ }
744812 }
745813
746- EssContextUpdateDisplay (ctx);
747814 EssContextRunEventLoopOnce (ctx);
748-
749- // Reduce CPU/GPU usage while the splash is static.
750- usleep (50 * 1000 );
815+ usleep (kEventLoopSleepUs );
751816 }
752817
753818 destroyProgramAndTexture (gl);
0 commit comments