1 /* 2 * Common display-related functions. 3 * 4 * Copyright (C) 2018 Paul Boddie <paul@boddie.org.uk> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "display.h" 21 #include "utils.h" 22 23 24 25 /* Initialise a display configuration. */ 26 27 void init_display(display_config_t *cfg) 28 { 29 init_frames(cfg); 30 init_display_properties(cfg); 31 } 32 33 /* Initialise the display constraints. */ 34 35 void init_display_properties(display_config_t *cfg) 36 { 37 /* Fixed address of the frame. */ 38 39 cfg->frame_start = display_get_frame_start(cfg, cfg->frame); 40 41 /* Floating address of the screen contents. */ 42 43 if (cfg->screen_starts) 44 cfg->screen_start = cfg->screen_starts[cfg->frame]; 45 46 /* Without any way of recording the address, just reset the start. */ 47 48 else 49 cfg->screen_start = cfg->frame_start; 50 51 /* Fixed limit of the frame. */ 52 53 cfg->screen_limit = cfg->frame_start + cfg->screen_size; 54 } 55 56 /* Initialise the screen start addresses for all frames. */ 57 58 void init_frames(display_config_t *cfg) 59 { 60 int frame; 61 62 for (frame = 0; frame < cfg->frames; frame++) 63 cfg->screen_starts[frame] = display_get_frame_start(cfg, frame); 64 } 65 66 67 68 /* Select a frame in the framebuffer. */ 69 70 void display_select_frame(display_config_t *cfg, int frame) 71 { 72 if ((frame < 0) || (frame >= cfg->frames)) 73 return; 74 75 /* Store the current frame's screen start, if possible. */ 76 77 if (cfg->screen_starts) 78 cfg->screen_starts[cfg->frame] = cfg->screen_start; 79 80 /* Update the frame details. */ 81 82 cfg->frame = frame; 83 84 /* Set the screen start offset when switching frames. */ 85 86 init_display_properties(cfg); 87 } 88 89 /* Select the next available frame in the framebuffer. */ 90 91 void display_select_next_frame(display_config_t *cfg) 92 { 93 display_select_frame(cfg, wrap_value(cfg->frame + 1, cfg->frames)); 94 } 95 96 /* Set the number of frames in the framebuffer memory. */ 97 98 void display_set_frames(display_config_t *cfg, int frames) 99 { 100 if ((frames <= 0) || (frames > cfg->max_frames)) 101 return; 102 103 /* Recalculate the number of lines. */ 104 105 cfg->line_count = (cfg->total_lines - cfg->max_frames) / frames; 106 107 /* Recalculate the screen size. */ 108 109 cfg->screen_size = cfg->line_count * cfg->line_length; 110 111 /* Set the number of frames and the current frame. */ 112 113 cfg->frames = frames; 114 cfg->frame = 0; 115 116 init_frames(cfg); 117 init_display_properties(cfg); 118 } 119 120 121 122 /* Return the start address of the given frame. */ 123 124 uint8_t *display_get_frame_start(display_config_t *cfg, int frame) 125 { 126 return cfg->framebuffer + (cfg->screen_size + cfg->line_length) * frame; 127 } 128 129 /* Return the line data position for the given pixel. */ 130 131 int display_get_position(display_config_t *cfg, int x) 132 { 133 int cell, offset, pos; 134 135 if (cfg->line_channels < 2) 136 return x; 137 138 /* Determine which cell is providing the position and the offset of the 139 pixel within the cell. */ 140 141 cell = x / cfg->cell_size; 142 offset = x % cfg->cell_size; 143 144 /* Determine the resulting position within the divided-up data. */ 145 146 pos = (cell / 2) * cfg->cell_size + offset; 147 148 /* Return the final position within the entire data. All cells in 149 odd-numbered positions occur in the first half, all even-numbered cells 150 in the second half. */ 151 152 return cell % 2 ? pos + cfg->line_length / 2 : pos; 153 } 154 155 /* Return the screen start offset. */ 156 157 uint32_t display_get_start_offset(display_config_t *cfg) 158 { 159 return cfg->screen_start - cfg->frame_start; 160 } 161 162 163 164 /* Copying from/to the display to/from a backing store. */ 165 166 void display_copy(display_config_t *cfg, uint8_t *store, 167 int width, int height, 168 int x, int y, int key, int to_display) 169 { 170 display_copy_section(cfg, store, width, height, 171 0, 0, width, height, 172 x, y, key, to_display); 173 } 174 175 /* Copying from/to the display to/from a backing store region. */ 176 177 void display_copy_section(display_config_t *cfg, uint8_t *store, 178 int width, int height, 179 int xstart, int ystart, int xsize, int ysize, 180 int x, int y, int key, int to_display) 181 { 182 int sx, sy, dx, dy; 183 uint8_t *storeline = store + ystart * width, 184 *displayline = display_wrap_pointer(cfg, cfg->screen_start + y * cfg->line_length), 185 pixel; 186 187 /* Define the limits of the copying in the store. */ 188 189 int xlimit = xstart + xsize, ylimit = ystart + ysize; 190 191 if (xlimit > width) 192 xlimit = width; 193 194 if (ylimit > height) 195 ylimit = height; 196 197 /* Perform the copying between the store and display. */ 198 199 for (sy = ystart, dy = y; (sy < ylimit) && (dy < cfg->line_count); sy += 1, dy++) 200 { 201 for (sx = xstart, dx = x; (sx < xlimit) && (dx < cfg->line_length); sx++, dx++) 202 { 203 if (to_display) 204 { 205 pixel = storeline[sx]; 206 if ((key < 0) || (pixel != key)) 207 displayline[display_get_position(cfg, dx)] = pixel; 208 } 209 else 210 storeline[sx] = displayline[display_get_position(cfg, dx)]; 211 } 212 213 storeline += width; 214 displayline = display_wrap_pointer(cfg, displayline + cfg->line_length); 215 } 216 } 217 218 /* Convenience function for copying. */ 219 220 static void copy_data(uint8_t *dest, const uint8_t *src, int count) 221 { 222 while (count--) 223 *dest++ = *src++; 224 } 225 226 /* Scroll the display. */ 227 228 void display_scroll(display_config_t *cfg, int x, int y) 229 { 230 /* Move the screen start by the given number of bytes and lines, wrapping 231 around the start and end of the framebuffer. */ 232 233 uint8_t *screen_start = cfg->screen_start + x + y * cfg->line_length; 234 uint32_t offset; 235 236 /* Test for the start pointer needing to be wrapped. Where wrapping around 237 the screen limits occurs, the data for the line referenced by the start 238 pointer needs copying to its new location. This is because a spare line 239 is used (due to line data needing to be contiguous), and upon wrapping 240 around, the newly-referenced line will not contain relevant data. 241 242 Such copying may be unnecessary if an entire pixel line is scrolled onto 243 the display. */ 244 245 if (screen_start < cfg->frame_start) 246 { 247 /* Get the extent to which the screen start (and the start of the first 248 display line) is off the start of the frame. */ 249 250 offset = cfg->frame_start - screen_start; 251 screen_start = display_wrap_pointer(cfg, screen_start); 252 253 /* The end of the first display line needs copying, with any data from 254 the frame start being transferred to the relocated line. */ 255 256 if (offset < cfg->line_length) 257 copy_data(screen_start + offset, cfg->frame_start, 258 cfg->line_length - offset); 259 } 260 else if (screen_start >= cfg->screen_limit) 261 { 262 /* Get the extent to which the screen start (and the start of the first 263 display line) is off the end of the frame. */ 264 265 offset = screen_start - cfg->screen_limit; 266 screen_start = display_wrap_pointer(cfg, screen_start); 267 268 /* The start of the first display line needs copying, with any data 269 before the frame end being transferred to the relocated line. */ 270 271 if (offset < cfg->line_length) 272 copy_data(screen_start, cfg->screen_limit + offset, 273 cfg->line_length - offset); 274 } 275 276 cfg->screen_start = screen_start; 277 } 278 279 /* Provide a pattern to test the line data. */ 280 281 void display_test_linedata(display_config_t *cfg) 282 { 283 int x, y; 284 uint8_t *linedata = cfg->screen_start; 285 286 for (y = 0; y < cfg->line_count; y++) 287 { 288 for (x = 0; x < cfg->line_length; x++) 289 { 290 /* Pixel: I0RRGGBB = Y0YYYYXX */ 291 292 linedata[display_get_position(cfg, x)] = (x % 2) ? 293 (((y / (cfg->line_count / 32)) & 0b1) << 7) | 294 (((y / (cfg->line_count / 16)) & 0b1111) << 2) | 295 ((x / (cfg->line_length / 4)) & 0b11) : 296 0x00; 297 } 298 299 linedata = display_wrap_pointer(cfg, linedata + cfg->line_length); 300 } 301 } 302 303 /* Wrap the screen pointer to the current frame. */ 304 305 uint8_t *display_wrap_pointer(display_config_t *cfg, uint8_t *ptr) 306 { 307 return wrap_pointer(ptr, cfg->frame_start, cfg->screen_limit); 308 }