diff --git a/dev-utils/macos-retina-display.md b/dev-utils/macos-retina-display.md new file mode 100644 index 00000000..ae464dc4 --- /dev/null +++ b/dev-utils/macos-retina-display.md @@ -0,0 +1,97 @@ +## Window creation for Retina displays + +The file info.plist sets NSHighResolutionCapable to true. This is fine for High-DPI +and retina displays. + +The `SDL_CreateWindow` is called with the flag `SDL_WINDOW_ALLOW_HIGHDPI`. +On Mac OS it means that, in source file `video/cocoa/SDL_cocoawindow.m`, from +function `Cocoa_CreateWindow`, SDL calls: + +```objc +/* highdpi will be TRUE below */ +BOOL highdpi = (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) != 0; +[contentView setWantsBestResolutionOpenGLSurface:highdpi] +``` + +Documentation for `setWantsBestResolutionOpenGLSurface`: + +https://developer.apple.com/documentation/appkit/nsview/1414938-wantsbestresolutionopenglsurface + +with more details in "OpenGL Programming Guide for Mac", chapter +"Optimizing OpenGL for High Resolution": + +https://developer.apple.com/library/archive/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/EnablingOpenGLforHighResolution/EnablingOpenGLforHighResolution.html#//apple_ref/doc/uid/TP40001987-CH1001-SW4 + +Citation from the official documentation: + +You can opt in to high resolution by calling the method +`setWantsBestResolutionOpenGLSurface:` when you initialize the view, and +supplying YES as an argument: + +```objc +[self setWantsBestResolutionOpenGLSurface:YES]; +``` + +If you don’t opt in, the system magnifies the rendered results. + +The wantsBestResolutionOpenGLSurface property is relevant only for views to +which an NSOpenGLContext object is bound. Its value does not affect the behavior +of other views. For compatibility, wantsBestResolutionOpenGLSurface defaults to +NO, providing a 1-pixel-per-point framebuffer regardless of the backing scale +factor for the display the view occupies. Setting this property to YES for a +given view causes AppKit to allocate a higher-resolution framebuffer when +appropriate for the backing scale factor and target display. + +To function correctly with wantsBestResolutionOpenGLSurface set to YES, a view +must perform correct conversions between view units (points) and pixel units as +needed. For example, the common practice of passing the width and height of +[self bounds] to glViewport() will yield incorrect results at high resolution, +because the parameters passed to the glViewport() function must be in pixels. As +a result, you’ll get only partial instead of complete coverage of the render +surface. Instead, use the backing store bounds: + +```objc +[self convertRectToBacking:[self bounds]]; +``` + +## Coordinates + +The SDL function `SDL_GL_GetDrawableSize` will provide the size of the underlying drawable +in pixels. From the `SDL_video.h` header. + +```c +/** + * \brief Get the size of a window's underlying drawable in pixels (for use + * with glViewport). + * + * \param window Window from which the drawable size should be queried + * \param w Pointer to variable for storing the width in pixels, may be NULL + * \param h Pointer to variable for storing the height in pixels, may be NULL + * + * This may differ from SDL_GetWindowSize() if we're rendering to a high-DPI + * drawable, i.e. the window was created with SDL_WINDOW_ALLOW_HIGHDPI on a + * platform with high-DPI support (Apple calls this "Retina"), and not disabled + * by the SDL_HINT_VIDEO_HIGHDPI_DISABLED hint. + * + * \sa SDL_GetWindowSize() + * \sa SDL_CreateWindow() + */ +extern DECLSPEC void SDLCALL SDL_GL_GetDrawableSize(SDL_Window * window, int *w, + int *h); +``` + +In turns it calls `Cocoa_GL_GetDrawableSize` from source file +`video/cocoa/SDL_cocoaopengl.m`. The function use the method +`[contentView convertRectToBacking:viewport]`: + +```objc + if (window->flags & SDL_WINDOW_ALLOW_HIGHDPI) { + /* This gives us the correct viewport for a Retina-enabled view, only + * supported on 10.7+. */ + if ([contentView respondsToSelector:@selector(convertRectToBacking:)]) { + viewport = [contentView convertRectToBacking:viewport]; + } + } +``` + +to give back the sizes in pixels. diff --git a/src/main.c b/src/main.c index e2ae548b..98dd6eb3 100644 --- a/src/main.c +++ b/src/main.c @@ -125,6 +125,9 @@ int main(int argc, char **argv) { window = SDL_CreateWindow( "", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dm.w * 0.8, dm.h * 0.8, SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN); + + fprintf(stderr, "New window: %p\n", window); fflush(stderr); + init_window_icon(); ren_init(window); diff --git a/src/renderer.c b/src/renderer.c index 36a76604..da406c20 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -33,6 +33,11 @@ struct RenFont { static SDL_Window *window; +static SDL_Renderer *window_renderer = NULL; +static SDL_Texture *window_texture = NULL; +static SDL_Surface *window_surface = NULL; +static int window_w = -1, window_h = -1; + static FR_Clip_Area clip; static void* check_alloc(void *ptr) { @@ -61,6 +66,30 @@ static const char* utf8_to_codepoint(const char *p, unsigned *dst) { } +static SDL_Surface *get_window_surface(SDL_Window *this_window) { + int w, h; + // fprintf(stderr, "get_window_surface: %p\n", this_window); fflush(stderr); + SDL_GL_GetDrawableSize(this_window, &w, &h); + // FIXME: check for errors ? + if (window_surface && w == window_w && h == window_h) { + fprintf(stderr, "get_window_surface: return current surface: %p\n", window_surface); fflush(stderr); + return window_surface; + } + if (window_surface) { + fprintf(stderr, "going to free: %p\n", window_surface); fflush(stderr); + SDL_FreeSurface(window_surface); + } + window_surface = SDL_CreateRGBSurfaceWithFormat(0, w, h, 24, SDL_PIXELFORMAT_BGR24); + + fprintf(stderr, "NEW surface: %p, size: %d %d\n", window_surface, w, h); fflush(stderr); + + window_w = w; + window_h = h; + + return window_surface; +} + + void ren_cp_replace_init(CPReplaceTable *rep_table) { rep_table->size = 0; rep_table->replacements = NULL; @@ -95,18 +124,42 @@ void ren_cp_replace_add(CPReplaceTable *rep_table, const char *src, const char * void ren_init(SDL_Window *win) { assert(win); window = win; - SDL_Surface *surf = SDL_GetWindowSurface(window); + SDL_Surface *surf = get_window_surface(window); //SDL_GetWindowSurface(window); + fprintf(stderr, "New surface %p\n", surf); fflush(stderr); ren_set_clip_rect( (RenRect) { 0, 0, surf->w, surf->h } ); } void ren_update_rects(RenRect *rects, int count) { +#if 0 SDL_UpdateWindowSurfaceRects(window, (SDL_Rect*) rects, count); +#endif + fprintf(stderr, "ren_update_rects\n"); fflush(stderr); static bool initial_frame = true; if (initial_frame) { SDL_ShowWindow(window); initial_frame = false; } + + int w, h; + SDL_GL_GetDrawableSize(window, &w, &h); + + if (window_renderer && (w != window_w || h != window_h)) { + SDL_DestroyTexture(window_texture); + SDL_DestroyRenderer(window_renderer); + window_renderer = NULL; + } + + if (!window_renderer) { + window_renderer = SDL_CreateRenderer(window, -1, 0); + // SDL_CreateTextureFromSurface(sdlRenderer, mySurface); + window_texture = SDL_CreateTexture(window_renderer, SDL_PIXELFORMAT_BGR24, SDL_TEXTUREACCESS_STREAMING, w, h); + fprintf(stderr, "got new renderer and texture: %p %p\n", window_renderer, window_texture); fflush(stderr); + } + // FIXME: we ignore the rects here. + SDL_UpdateTexture(window_texture, NULL, window_surface->pixels, window_w * 3); + SDL_RenderCopy(window_renderer, window_texture, NULL, NULL); + SDL_RenderPresent(window_renderer); } @@ -119,7 +172,7 @@ void ren_set_clip_rect(RenRect rect) { void ren_get_size(int *x, int *y) { - SDL_Surface *surf = SDL_GetWindowSurface(window); + SDL_Surface *surf = get_window_surface(window); //SDL_GetWindowSurface(window); *x = surf->w; *y = surf->h; } @@ -278,7 +331,10 @@ void ren_draw_rect(RenRect rect, RenColor color) { x2 = x2 > clip.right ? clip.right : x2; y2 = y2 > clip.bottom ? clip.bottom : y2; - SDL_Surface *surf = SDL_GetWindowSurface(window); + // fprintf(stderr, "ren_draw_rect: clipped rect: (%d, %d) (%d, %d)\n", x1, y1, x2, y2); + + // SDL_Surface *surf = SDL_GetWindowSurface(window); + SDL_Surface *surf = get_window_surface(window); RenColor *d = (RenColor*) surf->pixels; d += x1 + y1 * surf->w; int dr = surf->w - (x2 - x1); @@ -290,6 +346,8 @@ void ren_draw_rect(RenRect rect, RenColor color) { } } +// FIXME: this function is never used +#if 0 void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) { if (color.a == 0) { return; } @@ -304,7 +362,7 @@ void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) } /* draw */ - SDL_Surface *surf = SDL_GetWindowSurface(window); + SDL_Surface *surf = get_window_surface(window); //SDL_GetWindowSurface(window); RenColor *s = image->pixels; RenColor *d = (RenColor*) surf->pixels; s += sub->x + sub->y * image->width; @@ -322,6 +380,7 @@ void ren_draw_image(RenImage *image, RenRect *sub, int x, int y, RenColor color) s += sr; } } +#endif static int codepoint_replace(CPReplaceTable *rep_table, unsigned *codepoint) { for (int i = 0; i < rep_table->size; i++) { @@ -340,7 +399,7 @@ void ren_draw_text_subpixel(RenFont *font, const char *text, int x_subpixel, int { const char *p = text; unsigned codepoint; - SDL_Surface *surf = SDL_GetWindowSurface(window); + SDL_Surface *surf = get_window_surface(window); // SDL_GetWindowSurface(window); const FR_Color color_fr = { .r = color.r, .g = color.g, .b = color.b }; while (*p) { FR_Color color_rep;