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