core_viewport_scaling.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. /*******************************************************************************************
  2. *
  3. * raylib [core] example - viewport scaling
  4. *
  5. * Example complexity rating: [★★☆☆] 2/4
  6. *
  7. * Example originally created with raylib 5.5, last time updated with raylib 5.5
  8. *
  9. * Example contributed by Agnis Aldiņš (@nezvers) and reviewed by Ramon Santamaria (@raysan5)
  10. *
  11. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  12. * BSD-like license that allows static linking with closed source software
  13. *
  14. * Copyright (c) 2025 Agnis Aldiņš (@nezvers)
  15. *
  16. ********************************************************************************************/
  17. #include "raylib.h"
  18. #define RESOLUTION_COUNT 4 // For iteration purposes and teaching example
  19. typedef enum {
  20. // Only upscale, useful for pixel art
  21. KEEP_ASPECT_INTEGER,
  22. KEEP_HEIGHT_INTEGER,
  23. KEEP_WIDTH_INTEGER,
  24. // Can also downscale
  25. KEEP_ASPECT,
  26. KEEP_HEIGHT,
  27. KEEP_WIDTH,
  28. // For itteration purposes and as a teaching example
  29. VIEWPORT_TYPE_COUNT,
  30. } ViewportType;
  31. // For displaying on GUI
  32. const char *ViewportTypeNames[VIEWPORT_TYPE_COUNT] = {
  33. "KEEP_ASPECT_INTEGER",
  34. "KEEP_HEIGHT_INTEGER",
  35. "KEEP_WIDTH_INTEGER",
  36. "KEEP_ASPECT",
  37. "KEEP_HEIGHT",
  38. "KEEP_WIDTH",
  39. };
  40. //--------------------------------------------------------------------------------------
  41. // Module Functions Declaration
  42. //--------------------------------------------------------------------------------------
  43. static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect);
  44. static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect);
  45. static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect);
  46. static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect);
  47. static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect);
  48. static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect);
  49. static void ResizeRenderSize(ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target);
  50. // Example how to calculate position on RenderTexture
  51. static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle *textureRect, Rectangle *scaledRect);
  52. //------------------------------------------------------------------------------------
  53. // Program main entry point
  54. //------------------------------------------------------------------------------------
  55. int main(void)
  56. {
  57. // Initialization
  58. //---------------------------------------------------------
  59. int screenWidth = 800;
  60. int screenHeight = 450;
  61. SetConfigFlags(FLAG_WINDOW_RESIZABLE);
  62. InitWindow(screenWidth, screenHeight, "raylib [core] example - viewport scaling");
  63. // Preset resolutions that could be created by subdividing screen resolution
  64. Vector2 resolutionList[RESOLUTION_COUNT] = {
  65. (Vector2){ 64, 64 },
  66. (Vector2){ 256, 240 },
  67. (Vector2){ 320, 180 },
  68. // 4K doesn't work with integer scaling but included for example purposes with non-integer scaling
  69. (Vector2){ 3840, 2160 },
  70. };
  71. int resolutionIndex = 0;
  72. int gameWidth = 64;
  73. int gameHeight = 64;
  74. RenderTexture2D target = (RenderTexture2D){ 0 };
  75. Rectangle sourceRect = (Rectangle){ 0 };
  76. Rectangle destRect = (Rectangle){ 0 };
  77. ViewportType viewportType = KEEP_ASPECT_INTEGER;
  78. ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target);
  79. // Button rectangles
  80. Rectangle decreaseResolutionButton = (Rectangle){ 200, 30, 10, 10 };
  81. Rectangle increaseResolutionButton = (Rectangle){ 215, 30, 10, 10 };
  82. Rectangle decreaseTypeButton = (Rectangle){ 200, 45, 10, 10 };
  83. Rectangle increaseTypeButton = (Rectangle){ 215, 45, 10, 10 };
  84. SetTargetFPS(60); // Set our game to run at 60 frames-per-second
  85. //----------------------------------------------------------
  86. // Main game loop
  87. while (!WindowShouldClose()) // Detect window close button or ESC key
  88. {
  89. // Update
  90. //----------------------------------------------------------------------------------
  91. if (IsWindowResized()) ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target);
  92. Vector2 mousePosition = GetMousePosition();
  93. bool mousePressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT);
  94. // Check buttons and rescale
  95. if (CheckCollisionPointRec(mousePosition, decreaseResolutionButton) && mousePressed)
  96. {
  97. resolutionIndex = (resolutionIndex + RESOLUTION_COUNT - 1)%RESOLUTION_COUNT;
  98. gameWidth = resolutionList[resolutionIndex].x;
  99. gameHeight = resolutionList[resolutionIndex].y;
  100. ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target);
  101. }
  102. if (CheckCollisionPointRec(mousePosition, increaseResolutionButton) && mousePressed)
  103. {
  104. resolutionIndex = (resolutionIndex + 1)%RESOLUTION_COUNT;
  105. gameWidth = resolutionList[resolutionIndex].x;
  106. gameHeight = resolutionList[resolutionIndex].y;
  107. ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target);
  108. }
  109. if (CheckCollisionPointRec(mousePosition, decreaseTypeButton) && mousePressed)
  110. {
  111. viewportType = (viewportType + VIEWPORT_TYPE_COUNT - 1)%VIEWPORT_TYPE_COUNT;
  112. ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target);
  113. }
  114. if (CheckCollisionPointRec(mousePosition, increaseTypeButton) && mousePressed)
  115. {
  116. viewportType = (viewportType + 1)%VIEWPORT_TYPE_COUNT;
  117. ResizeRenderSize(viewportType, &screenWidth, &screenHeight, gameWidth, gameHeight, &sourceRect, &destRect, &target);
  118. }
  119. Vector2 textureMousePosition = Screen2RenderTexturePosition(mousePosition, &sourceRect, &destRect);
  120. //----------------------------------------------------------------------------------
  121. // Draw
  122. //----------------------------------------------------------------------------------
  123. // Draw our scene to the render texture
  124. BeginTextureMode(target);
  125. ClearBackground(WHITE);
  126. DrawCircle(textureMousePosition.x, textureMousePosition.y, 20.0f, LIME);
  127. EndTextureMode();
  128. // Draw render texture to main framebuffer
  129. BeginDrawing();
  130. ClearBackground(BLACK);
  131. // Draw our render texture with rotation applied
  132. DrawTexturePro(target.texture, sourceRect, destRect, (Vector2){ 0.0f, 0.0f }, 0.0f, WHITE);
  133. // Draw Native resolution (GUI or anything)
  134. // Draw info box
  135. Rectangle infoRect = (Rectangle){5, 5, 330, 105};
  136. DrawRectangleRec(infoRect, Fade(LIGHTGRAY, 0.7f));
  137. DrawRectangleLines(infoRect.x, infoRect.y, infoRect.width, infoRect.height, BLUE);
  138. DrawText(TextFormat("Window Resolution: %d x %d", screenWidth, screenHeight), 15, 15, 10, BLACK);
  139. DrawText(TextFormat("Game Resolution: %d x %d", gameWidth, gameHeight), 15, 30, 10, BLACK);
  140. DrawText(TextFormat("Type: %s", ViewportTypeNames[viewportType]), 15, 45, 10, BLACK);
  141. Vector2 scaleRatio = (Vector2){destRect.width/sourceRect.width, -destRect.height/sourceRect.height};
  142. if (scaleRatio.x < 0.001f || scaleRatio.y < 0.001f) DrawText(TextFormat("Scale ratio: INVALID"), 15, 60, 10, BLACK);
  143. else DrawText(TextFormat("Scale ratio: %.2f x %.2f", scaleRatio.x, scaleRatio.y), 15, 60, 10, BLACK);
  144. DrawText(TextFormat("Source size: %.2f x %.2f", sourceRect.width, -sourceRect.height), 15, 75, 10, BLACK);
  145. DrawText(TextFormat("Destination size: %.2f x %.2f", destRect.width, destRect.height), 15, 90, 10, BLACK);
  146. // Draw buttons
  147. DrawRectangleRec(decreaseTypeButton, SKYBLUE);
  148. DrawRectangleRec(increaseTypeButton, SKYBLUE);
  149. DrawRectangleRec(decreaseResolutionButton, SKYBLUE);
  150. DrawRectangleRec(increaseResolutionButton, SKYBLUE);
  151. DrawText("<", decreaseTypeButton.x + 3, decreaseTypeButton.y + 1, 10, BLACK);
  152. DrawText(">", increaseTypeButton.x + 3, increaseTypeButton.y + 1, 10, BLACK);
  153. DrawText("<", decreaseResolutionButton.x + 3, decreaseResolutionButton.y + 1, 10, BLACK);
  154. DrawText(">", increaseResolutionButton.x + 3, increaseResolutionButton.y + 1, 10, BLACK);
  155. EndDrawing();
  156. //----------------------------------------------------------------------------------
  157. }
  158. // De-Initialization
  159. //----------------------------------------------------------------------------------
  160. CloseWindow(); // Close window and OpenGL context
  161. //----------------------------------------------------------------------------------
  162. return 0;
  163. }
  164. //--------------------------------------------------------------------------------------
  165. // Module Functions Definition
  166. //--------------------------------------------------------------------------------------
  167. static void KeepAspectCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect)
  168. {
  169. sourceRect->x = 0.0f;
  170. sourceRect->y = (float)gameHeight;
  171. sourceRect->width = (float)gameWidth;
  172. sourceRect->height = (float)-gameHeight;
  173. const int ratio_x = (screenWidth/gameWidth);
  174. const int ratio_y = (screenHeight/gameHeight);
  175. const float resizeRatio = (float)((ratio_x < ratio_y)? ratio_x : ratio_y);
  176. destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f);
  177. destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f);
  178. destRect->width = (float)(int)(gameWidth*resizeRatio);
  179. destRect->height = (float)(int)(gameHeight*resizeRatio);
  180. }
  181. static void KeepHeightCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect)
  182. {
  183. const float resizeRatio = (float)(screenHeight/gameHeight);
  184. sourceRect->x = 0.0f;
  185. sourceRect->y = 0.0f;
  186. sourceRect->width = (float)(int)(screenWidth/resizeRatio);
  187. sourceRect->height = (float)-gameHeight;
  188. destRect->x = (float)(int)((screenWidth - (sourceRect->width*resizeRatio))*0.5f);
  189. destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f);
  190. destRect->width = (float)(int)(sourceRect->width*resizeRatio);
  191. destRect->height = (float)(int)(gameHeight*resizeRatio);
  192. }
  193. static void KeepWidthCenteredInteger(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect)
  194. {
  195. const float resizeRatio = (float)(screenWidth/gameWidth);
  196. sourceRect->x = 0.0f;
  197. sourceRect->y = 0.0f;
  198. sourceRect->width = (float)gameWidth;
  199. sourceRect->height = (float)(int)(screenHeight/resizeRatio);
  200. destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f);
  201. destRect->y = (float)(int)((screenHeight - (sourceRect->height*resizeRatio))*0.5f);
  202. destRect->width = (float)(int)(gameWidth*resizeRatio);
  203. destRect->height = (float)(int)(sourceRect->height*resizeRatio);
  204. sourceRect->height *= -1.0f;
  205. }
  206. static void KeepAspectCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect)
  207. {
  208. sourceRect->x = 0.0f;
  209. sourceRect->y = (float)gameHeight;
  210. sourceRect->width = (float)gameWidth;
  211. sourceRect->height = (float)-gameHeight;
  212. const float ratio_x = ((float)screenWidth/(float)gameWidth);
  213. const float ratio_y = ((float)screenHeight/(float)gameHeight);
  214. const float resizeRatio = (ratio_x < ratio_y ? ratio_x : ratio_y);
  215. destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f);
  216. destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f);
  217. destRect->width = (float)(int)(gameWidth*resizeRatio);
  218. destRect->height = (float)(int)(gameHeight*resizeRatio);
  219. }
  220. static void KeepHeightCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect)
  221. {
  222. const float resizeRatio = ((float)screenHeight/(float)gameHeight);
  223. sourceRect->x = 0.0f;
  224. sourceRect->y = 0.0f;
  225. sourceRect->width = (float)(int)((float)screenWidth/resizeRatio);
  226. sourceRect->height = (float)-gameHeight;
  227. destRect->x = (float)(int)((screenWidth - (sourceRect->width*resizeRatio))*0.5f);
  228. destRect->y = (float)(int)((screenHeight - (gameHeight*resizeRatio))*0.5f);
  229. destRect->width = (float)(int)(sourceRect->width*resizeRatio);
  230. destRect->height = (float)(int)(gameHeight*resizeRatio);
  231. }
  232. static void KeepWidthCentered(int screenWidth, int screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect)
  233. {
  234. const float resizeRatio = ((float)screenWidth/(float)gameWidth);
  235. sourceRect->x = 0.0f;
  236. sourceRect->y = 0.0f;
  237. sourceRect->width = (float)gameWidth;
  238. sourceRect->height = (float)(int)((float)screenHeight/resizeRatio);
  239. destRect->x = (float)(int)((screenWidth - (gameWidth*resizeRatio))*0.5f);
  240. destRect->y = (float)(int)((screenHeight - (sourceRect->height*resizeRatio))*0.5f);
  241. destRect->width = (float)(int)(gameWidth*resizeRatio);
  242. destRect->height = (float)(int)(sourceRect->height*resizeRatio);
  243. sourceRect->height *= -1.f;
  244. }
  245. static void ResizeRenderSize(ViewportType viewportType, int *screenWidth, int *screenHeight, int gameWidth, int gameHeight, Rectangle *sourceRect, Rectangle *destRect, RenderTexture2D *target)
  246. {
  247. *screenWidth = GetScreenWidth();
  248. *screenHeight = GetScreenHeight();
  249. switch(viewportType)
  250. {
  251. case KEEP_ASPECT_INTEGER: KeepAspectCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break;
  252. case KEEP_HEIGHT_INTEGER: KeepHeightCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break;
  253. case KEEP_WIDTH_INTEGER: KeepWidthCenteredInteger(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break;
  254. case KEEP_ASPECT: KeepAspectCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break;
  255. case KEEP_HEIGHT: KeepHeightCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break;
  256. case KEEP_WIDTH: KeepWidthCentered(*screenWidth, *screenHeight, gameWidth, gameHeight, sourceRect, destRect); break;
  257. default: break;
  258. }
  259. UnloadRenderTexture(*target);
  260. *target = LoadRenderTexture(sourceRect->width, -sourceRect->height);
  261. }
  262. // Example how to calculate position on RenderTexture
  263. static Vector2 Screen2RenderTexturePosition(Vector2 point, Rectangle *textureRect, Rectangle *scaledRect)
  264. {
  265. Vector2 relativePosition = {point.x - scaledRect->x, point.y - scaledRect->y};
  266. Vector2 ratio = {textureRect->width/scaledRect->width, -textureRect->height/scaledRect->height};
  267. return (Vector2){relativePosition.x*ratio.x, relativePosition.y*ratio.x};
  268. }