pick up CXXFLAGS passed on commandline
[rofl0r-df-libgraphics.git] / g_src / renderer_2d.hpp
blobd08816280614d6f62ee4660a39ce4047cc93228a
1 #include "enabler.h"
2 #include "init.h"
3 #include "resize++.h"
4 #include "ttf_manager.hpp"
6 #include <iostream>
7 using namespace std;
9 void report_error(const char*, const char*);
11 class renderer_2d_base : public renderer {
12 protected:
13 SDL_Surface *screen;
14 map<texture_fullid, SDL_Surface*> tile_cache;
15 int dispx, dispy, dimx, dimy;
16 // We may shrink or enlarge dispx/dispy in response to zoom requests. dispx/y_z are the
17 // size we actually display tiles at.
18 int dispx_z, dispy_z;
19 // Viewport origin
20 int origin_x, origin_y;
22 SDL_Surface *tile_cache_lookup(texture_fullid &id, bool convert=true) {
23 map<texture_fullid, SDL_Surface*>::iterator it = tile_cache.find(id);
24 if (it != tile_cache.end()) {
25 return it->second;
26 } else {
27 // Create the colorized texture
28 SDL_Surface *tex = enabler.textures.get_texture_data(id.texpos);
29 SDL_Surface *color;
30 color = SDL_CreateRGBSurface(SDL_SWSURFACE,
31 tex->w, tex->h,
32 tex->format->BitsPerPixel,
33 tex->format->Rmask,
34 tex->format->Gmask,
35 tex->format->Bmask,
36 0);
37 if (!color) {
38 MessageBox (NULL, "Unable to create texture!", "Fatal error", MB_OK | MB_ICONEXCLAMATION);
39 abort();
42 // Fill it
43 Uint32 color_fgi = SDL_MapRGB(color->format, id.r*255, id.g*255, id.b*255);
44 Uint8 *color_fg = (Uint8*) &color_fgi;
45 Uint32 color_bgi = SDL_MapRGB(color->format, id.br*255, id.bg*255, id.bb*255);
46 Uint8 *color_bg = (Uint8*) &color_bgi;
47 SDL_LockSurface(tex);
48 SDL_LockSurface(color);
50 Uint8 *pixel_src, *pixel_dst;
51 for (int y = 0; y < tex->h; y++) {
52 pixel_src = ((Uint8*)tex->pixels) + (y * tex->pitch);
53 pixel_dst = ((Uint8*)color->pixels) + (y * color->pitch);
54 for (int x = 0; x < tex->w; x++, pixel_src+=4, pixel_dst+=4) {
55 float alpha = pixel_src[3] / 255.0;
56 for (int c = 0; c < 3; c++) {
57 float fg = color_fg[c] / 255.0, bg = color_bg[c] / 255.0, tex = pixel_src[c] / 255.0;
58 pixel_dst[c] = ((alpha * (tex * fg)) + ((1 - alpha) * bg)) * 255;
63 SDL_UnlockSurface(color);
64 SDL_UnlockSurface(tex);
66 SDL_Surface *disp = convert ?
67 SDL_Resize(color, dispx_z, dispy_z) : // Convert to display format; deletes color
68 color; // color is not deleted, but we don't want it to be.
69 // Insert and return
70 tile_cache[id] = disp;
71 return disp;
75 virtual bool init_video(int w, int h) {
76 // Get ourselves a 2D SDL window
77 Uint32 flags = init.display.flag.has_flag(INIT_DISPLAY_FLAG_2DHW) ? SDL_HWSURFACE : SDL_SWSURFACE;
78 flags |= init.display.flag.has_flag(INIT_DISPLAY_FLAG_2DASYNC) ? SDL_ASYNCBLIT : 0;
80 // Set it up for windowed or fullscreen, depending.
81 if (enabler.is_fullscreen()) {
82 flags |= SDL_FULLSCREEN;
83 } else {
84 if (!init.display.flag.has_flag(INIT_DISPLAY_FLAG_NOT_RESIZABLE))
85 flags |= SDL_RESIZABLE;
88 // (Re)create the window
89 screen = SDL_SetVideoMode(w, h, 32, flags);
90 if (screen == NULL) cout << "INIT FAILED!" << endl;
92 return screen != NULL;
95 public:
96 list<pair<SDL_Surface*,SDL_Rect> > ttfs_to_render;
98 void update_tile(int x, int y) {
99 // Figure out where to blit
100 SDL_Rect dst;
101 dst.x = dispx_z * x + origin_x;
102 dst.y = dispy_z * y + origin_y;
103 // Read tiles from gps, create cached texture
104 Either<texture_fullid,texture_ttfid> id = screen_to_texid(x, y);
105 SDL_Surface *tex;
106 if (id.isL) { // Ordinary tile, cached here
107 tex = tile_cache_lookup(id.left);
108 // And blit.
109 SDL_BlitSurface(tex, NULL, screen, &dst);
110 } else { // TTF, cached in ttf_manager so no point in also caching here
111 tex = ttf_manager.get_texture(id.right);
112 // Blit later
113 ttfs_to_render.push_back(make_pair(tex, dst));
117 void update_all() {
118 SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format, 0, 0, 0));
119 for (int x = 0; x < gps.dimx; x++)
120 for (int y = 0; y < gps.dimy; y++)
121 update_tile(x, y);
124 virtual void render() {
125 // Render the TTFs, which we left for last
126 for (auto it = ttfs_to_render.begin(); it != ttfs_to_render.end(); ++it) {
127 SDL_BlitSurface(it->first, NULL, screen, &it->second);
129 ttfs_to_render.clear();
130 // And flip out.
131 SDL_Flip(screen);
134 virtual ~renderer_2d_base() {
135 for (auto it = tile_cache.cbegin(); it != tile_cache.cend(); ++it)
136 SDL_FreeSurface(it->second);
137 for (auto it = ttfs_to_render.cbegin(); it != ttfs_to_render.cend(); ++it)
138 SDL_FreeSurface(it->first);
141 void grid_resize(int w, int h) {
142 dimx = w; dimy = h;
143 // Only reallocate the grid if it actually changes
144 if (init.display.grid_x != dimx || init.display.grid_y != dimy)
145 gps_allocate(dimx, dimy);
146 // But always force a full display cycle
147 gps.force_full_display_count = 1;
148 enabler.flag |= ENABLERFLAG_RENDER;
151 renderer_2d_base() {
152 zoom_steps = forced_steps = 0;
155 int zoom_steps, forced_steps;
156 int natural_w, natural_h;
158 void compute_forced_zoom() {
159 forced_steps = 0;
160 pair<int,int> zoomed = compute_zoom();
161 while (zoomed.first < MIN_GRID_X || zoomed.second < MIN_GRID_Y) {
162 forced_steps++;
163 zoomed = compute_zoom();
165 while (zoomed.first > MAX_GRID_X || zoomed.second > MAX_GRID_Y) {
166 forced_steps--;
167 zoomed = compute_zoom();
171 pair<int,int> compute_zoom(bool clamp = false) {
172 const int dispx = enabler.is_fullscreen() ?
173 init.font.large_font_dispx :
174 init.font.small_font_dispx;
175 const int dispy = enabler.is_fullscreen() ?
176 init.font.large_font_dispy :
177 init.font.small_font_dispy;
178 int w, h;
179 if (dispx < dispy) {
180 w = natural_w + zoom_steps + forced_steps;
181 h = double(natural_h) * (double(w) / double(natural_w));
182 } else {
183 h = natural_h + zoom_steps + forced_steps;
184 w = double(natural_w) * (double(h) / double(natural_h));
186 if (clamp) {
187 w = MIN(MAX(w, MIN_GRID_X), MAX_GRID_X);
188 h = MIN(MAX(h, MIN_GRID_Y), MAX_GRID_Y);
190 return make_pair(w,h);
194 void resize(int w, int h) {
195 // We've gotten resized.. first step is to reinitialize video
196 cout << "New window size: " << w << "x" << h << endl;
197 init_video(w, h);
198 dispx = enabler.is_fullscreen() ?
199 init.font.large_font_dispx :
200 init.font.small_font_dispx;
201 dispy = enabler.is_fullscreen() ?
202 init.font.large_font_dispy :
203 init.font.small_font_dispy;
204 cout << "Font size: " << dispx << "x" << dispy << endl;
205 // If grid size is currently overridden, we don't change it
206 if (enabler.overridden_grid_sizes.size() == 0) {
207 // (Re)calculate grid-size
208 dimx = MIN(MAX(w / dispx, MIN_GRID_X), MAX_GRID_X);
209 dimy = MIN(MAX(h / dispy, MIN_GRID_Y), MAX_GRID_Y);
210 cout << "Resizing grid to " << dimx << "x" << dimy << endl;
211 grid_resize(dimx, dimy);
213 // Calculate zoomed tile size
214 natural_w = MAX(w / dispx,1);
215 natural_h = MAX(h / dispy,1);
216 compute_forced_zoom();
217 reshape(compute_zoom(true));
218 cout << endl;
221 void reshape(pair<int,int> max_grid) {
222 int w = max_grid.first,
223 h = max_grid.second;
224 // Compute the largest tile size that will fit this grid into the window, roughly maintaining aspect ratio
225 double try_x = dispx, try_y = dispy;
226 try_x = screen->w / w;
227 try_y = MIN(try_x / dispx * dispy, screen->h / h);
228 try_x = MIN(try_x, try_y / dispy * dispx);
229 dispx_z = MAX(1,try_x); dispy_z = MAX(try_y,1);
230 cout << "Resizing font to " << dispx_z << "x" << dispy_z << endl;
231 // Remove now-obsolete tile catalog
232 for (map<texture_fullid, SDL_Surface*>::iterator it = tile_cache.begin();
233 it != tile_cache.end();
234 ++it)
235 SDL_FreeSurface(it->second);
236 tile_cache.clear();
237 // Recompute grid based on the new tile size
238 w = CLAMP(screen->w / dispx_z, MIN_GRID_X, MAX_GRID_X);
239 h = CLAMP(screen->h / dispy_z, MIN_GRID_Y, MAX_GRID_Y);
240 // Reset grid size
241 #ifdef DEBUG
242 cout << "Resizing grid to " << w << "x" << h << endl;
243 #endif
244 gps_allocate(w,h);
245 // Force redisplay
246 gps.force_full_display_count = 1;
247 // Calculate viewport origin, for centering
248 origin_x = (screen->w - dispx_z * w) / 2;
249 origin_y = (screen->h - dispy_z * h) / 2;
250 // Reset TTF rendering
251 ttf_manager.init(dispy_z, dispx_z);
254 private:
256 void set_fullscreen() {
257 if (enabler.is_fullscreen()) {
258 init.display.desired_windowed_width = screen->w;
259 init.display.desired_windowed_height = screen->h;
260 resize(init.display.desired_fullscreen_width,
261 init.display.desired_fullscreen_height);
262 } else {
263 resize(init.display.desired_windowed_width, init.display.desired_windowed_height);
267 bool get_mouse_coords(int &x, int &y) {
268 int mouse_x, mouse_y;
269 SDL_GetMouseState(&mouse_x, &mouse_y);
270 mouse_x -= origin_x; mouse_y -= origin_y;
271 if (mouse_x < 0 || mouse_x >= dispx_z*dimx ||
272 mouse_y < 0 || mouse_y >= dispy_z*dimy)
273 return false;
274 x = mouse_x / dispx_z;
275 y = mouse_y / dispy_z;
276 return true;
279 void zoom(zoom_commands cmd) {
280 pair<int,int> before = compute_zoom(true);
281 int before_steps = zoom_steps;
282 switch (cmd) {
283 case zoom_in: zoom_steps -= init.input.zoom_speed; break;
284 case zoom_out: zoom_steps += init.input.zoom_speed; break;
285 case zoom_reset:
286 zoom_steps = 0;
287 case zoom_resetgrid:
288 compute_forced_zoom();
289 break;
291 pair<int,int> after = compute_zoom(true);
292 if (after == before && (cmd == zoom_in || cmd == zoom_out))
293 zoom_steps = before_steps;
294 else
295 reshape(after);
300 class renderer_2d : public renderer_2d_base {
301 public:
302 renderer_2d() {
303 // Disable key repeat
304 SDL_EnableKeyRepeat(0, 0);
305 // Set window title/icon.
306 SDL_WM_SetCaption(GAME_TITLE_STRING, NULL);
307 SDL_Surface *icon = IMG_Load("data/art/icon.png");
308 if (icon != NULL) {
309 SDL_WM_SetIcon(icon, NULL);
310 // The icon's surface doesn't get used past this point.
311 SDL_FreeSurface(icon);
314 // Find the current desktop resolution if fullscreen resolution is auto
315 if (init.display.desired_fullscreen_width == 0 ||
316 init.display.desired_fullscreen_height == 0) {
317 const struct SDL_VideoInfo *info = SDL_GetVideoInfo();
318 init.display.desired_fullscreen_width = info->current_w;
319 init.display.desired_fullscreen_height = info->current_h;
322 // Initialize our window
323 bool worked = init_video(enabler.is_fullscreen() ?
324 init.display.desired_fullscreen_width :
325 init.display.desired_windowed_width,
326 enabler.is_fullscreen() ?
327 init.display.desired_fullscreen_height :
328 init.display.desired_windowed_height);
330 // Fallback to windowed mode if fullscreen fails
331 if (!worked && enabler.is_fullscreen()) {
332 enabler.fullscreen = false;
333 report_error("SDL initialization failure, trying windowed mode", SDL_GetError());
334 worked = init_video(init.display.desired_windowed_width,
335 init.display.desired_windowed_height);
337 // Quit if windowed fails
338 if (!worked) {
339 report_error("SDL initialization failure", SDL_GetError());
340 exit(EXIT_FAILURE);
345 class renderer_offscreen : public renderer_2d_base {
346 virtual bool init_video(int, int);
347 public:
348 virtual ~renderer_offscreen();
349 renderer_offscreen(int, int);
350 void update_all(int, int);
351 void save_to_file(const string &file);