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, uint8_t *framebuffer, 28 uint32_t line_length, uint32_t line_count, 29 int frames, uint8_t **screen_starts) 30 { 31 /* Framebuffer address. */ 32 33 cfg->framebuffer = framebuffer; 34 35 /* Frame allocation limits. */ 36 37 cfg->total_lines = (line_count + 1) * frames; 38 cfg->max_frames = frames; 39 40 /* Screen size and dimensions. */ 41 42 cfg->screen_size = line_length * line_count; 43 cfg->line_length = line_length; 44 cfg->line_count = line_count; 45 46 /* Set the number of frames and the current frame. */ 47 48 cfg->frames = frames; 49 cfg->frame = 0; 50 51 /* Set the pointer to a collection of screen start pointers, one per 52 frame. */ 53 54 cfg->screen_starts = screen_starts; 55 56 init_frames(cfg); 57 init_display_properties(cfg); 58 } 59 60 /* Initialise the display constraints. */ 61 62 void init_display_properties(display_config_t *cfg) 63 { 64 /* Fixed address of the frame. */ 65 66 cfg->frame_start = display_get_frame_start(cfg, cfg->frame); 67 68 /* Floating address of the screen contents. */ 69 70 if (cfg->screen_starts) 71 cfg->screen_start = cfg->screen_starts[cfg->frame]; 72 73 /* Without any way of recording the address, just reset the start. */ 74 75 else 76 cfg->screen_start = cfg->frame_start; 77 78 /* Fixed limit of the frame. */ 79 80 cfg->screen_limit = cfg->frame_start + cfg->screen_size; 81 82 /* Recalculate the line multiplier. */ 83 84 cfg->line_multiplier = cfg->scanlines / cfg->line_count; 85 } 86 87 /* Initialise the screen start addresses for all frames. */ 88 89 void init_frames(display_config_t *cfg) 90 { 91 int frame; 92 93 for (frame = 0; frame < cfg->frames; frame++) 94 cfg->screen_starts[frame] = display_get_frame_start(cfg, frame); 95 } 96 97 98 99 /* Select a frame in the framebuffer. */ 100 101 void display_select_frame(display_config_t *cfg, int frame) 102 { 103 if ((frame < 0) || (frame >= cfg->frames)) 104 return; 105 106 /* Store the current frame's screen start, if possible. */ 107 108 if (cfg->screen_starts) 109 cfg->screen_starts[cfg->frame] = cfg->screen_start; 110 111 /* Update the frame details. */ 112 113 cfg->frame = frame; 114 115 /* Set the screen start offset when switching frames. */ 116 117 init_display_properties(cfg); 118 } 119 120 /* Select the next available frame in the framebuffer. */ 121 122 void display_select_next_frame(display_config_t *cfg) 123 { 124 display_select_frame(cfg, wrap_value(cfg->frame + 1, cfg->frames)); 125 } 126 127 /* Set the number of frames in the framebuffer memory. */ 128 129 void display_set_frames(display_config_t *cfg, int frames) 130 { 131 if ((frames <= 0) || (frames > cfg->max_frames)) 132 return; 133 134 /* Recalculate the number of lines. */ 135 136 cfg->line_count = (cfg->total_lines - cfg->max_frames) / frames; 137 138 /* Recalculate the screen size. */ 139 140 cfg->screen_size = cfg->line_count * cfg->line_length; 141 142 /* Set the number of frames and the current frame. */ 143 144 cfg->frames = frames; 145 cfg->frame = 0; 146 147 init_frames(cfg); 148 init_display_properties(cfg); 149 } 150 151 152 153 /* Return the start address of the given frame. */ 154 155 uint8_t *display_get_frame_start(display_config_t *cfg, int frame) 156 { 157 return cfg->framebuffer + (cfg->screen_size + cfg->line_length) * frame; 158 } 159 160 /* Return the line data position for the given pixel. */ 161 162 int display_get_position(display_config_t *cfg, int x) 163 { 164 int cell, offset, pos; 165 166 if (cfg->line_channels < 2) 167 return x; 168 169 /* Determine which cell is providing the position and the offset of the 170 pixel within the cell. */ 171 172 cell = x / cfg->cell_size; 173 offset = x % cfg->cell_size; 174 175 /* Determine the resulting position within the divided-up data. */ 176 177 pos = (cell / 2) * cfg->cell_size + offset; 178 179 /* Return the final position within the entire data. All cells in 180 odd-numbered positions occur in the first half, all even-numbered cells 181 in the second half. */ 182 183 return cell % 2 ? pos + cfg->line_length / 2 : pos; 184 } 185 186 /* Return the screen start offset. */ 187 188 uint32_t display_get_start_offset(display_config_t *cfg) 189 { 190 return cfg->screen_start - cfg->frame_start; 191 } 192 193 194 195 /* Copying from/to the display to/from a backing store. */ 196 197 void display_copy(display_config_t *cfg, uint8_t *store, 198 int width, int height, int ystep, 199 int x, int y, int key, int to_display) 200 { 201 display_copy_section(cfg, store, width, height, 202 0, 0, width, height, ystep, 203 x, y, key, to_display); 204 } 205 206 /* Copying from/to the display to/from a backing store region. */ 207 208 void display_copy_section(display_config_t *cfg, uint8_t *store, 209 int width, int height, 210 int xstart, int ystart, int xsize, int ysize, int ystep, 211 int x, int y, int key, int to_display) 212 { 213 int sx, sy, dx, dy; 214 uint8_t *storeline = store + ystart * width, 215 *displayline = display_wrap_pointer(cfg, cfg->screen_start + y * cfg->line_length), 216 pixel; 217 218 /* Define the limits of the copying in the store. */ 219 220 int xlimit = xstart + xsize, ylimit = ystart + ysize; 221 222 if (xlimit > width) 223 xlimit = width; 224 225 if (ylimit > height) 226 ylimit = height; 227 228 /* Perform the copying between the store and display. */ 229 230 for (sy = ystart, dy = y; (sy < ylimit) && (dy < cfg->line_count); sy += ystep, dy++) 231 { 232 for (sx = xstart, dx = x; (sx < xlimit) && (dx < cfg->line_length); sx++, dx++) 233 { 234 if (to_display) 235 { 236 pixel = storeline[sx]; 237 if ((key < 0) || (pixel != key)) 238 displayline[display_get_position(cfg, dx)] = pixel; 239 } 240 else 241 storeline[sx] = displayline[display_get_position(cfg, dx)]; 242 } 243 244 storeline += width * ystep; 245 displayline = display_wrap_pointer(cfg, displayline + cfg->line_length); 246 } 247 } 248 249 /* Scroll the display. */ 250 251 void display_scroll(display_config_t *cfg, int x, int y) 252 { 253 /* Move the screen start by the given number of bytes and lines, wrapping 254 around the start and end of the framebuffer. */ 255 256 cfg->screen_start = display_wrap_pointer(cfg, cfg->screen_start + x + 257 y * cfg->line_length); 258 } 259 260 /* Provide a pattern to test the line data. */ 261 262 void display_test_linedata(display_config_t *cfg) 263 { 264 int x, y; 265 uint8_t *linedata = cfg->screen_start; 266 267 for (y = 0; y < cfg->line_count; y++) 268 { 269 for (x = 0; x < cfg->line_length; x++) 270 { 271 /* Pixel: I0RRGGBB = Y0YYYYXX */ 272 273 linedata[display_get_position(cfg, x)] = (x % 2) ? 274 (((y / (cfg->line_count / 32)) & 0b1) << 7) | 275 (((y / (cfg->line_count / 16)) & 0b1111) << 2) | 276 ((x / (cfg->line_length / 4)) & 0b11) : 277 0x00; 278 } 279 280 linedata = display_wrap_pointer(cfg, linedata + cfg->line_length); 281 } 282 } 283 284 /* Wrap the screen pointer to the current frame. */ 285 286 uint8_t *display_wrap_pointer(display_config_t *cfg, uint8_t *ptr) 287 { 288 return wrap_pointer(ptr, cfg->frame_start, cfg->screen_limit); 289 }