ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/Unix/video_vosf.h
Revision: 1.47
Committed: 2004-12-18T22:20:27Z (19 years, 6 months ago) by gbeauche
Content type: text/plain
Branch: MAIN
Changes since 1.46: +2 -2 lines
Log Message:
s/vm_page_size/vm_get_page_size/ to avoid name clash on MacOS X

File Contents

# Content
1 /*
2 * video_vosf.h - Video/graphics emulation, video on SEGV signals support
3 *
4 * Basilisk II (C) 1997-2004 Christian Bauer
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 2 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, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 */
20
21 #ifndef VIDEO_VOSF_H
22 #define VIDEO_VOSF_H
23
24 // Note: this file must be #include'd only in video_x.cpp
25 #ifdef ENABLE_VOSF
26
27 #include "sigsegv.h"
28 #include "vm_alloc.h"
29 #ifdef _WIN32
30 #include "util_windows.h"
31 #endif
32
33 // Glue for SDL and X11 support
34 #ifdef USE_SDL_VIDEO
35 #define MONITOR_INIT SDL_monitor_desc &monitor
36 #define VIDEO_DRV_INIT driver_window *drv
37 #define VIDEO_DRV_ROW_BYTES drv->s->pitch
38 #define VIDEO_DRV_LOCK_PIXELS if (SDL_MUSTLOCK(drv->s)) SDL_LockSurface(drv->s)
39 #define VIDEO_DRV_UNLOCK_PIXELS if (SDL_MUSTLOCK(drv->s)) SDL_UnlockSurface(drv->s)
40 #else
41 #ifdef SHEEPSHAVER
42 #define MONITOR_INIT /* nothing */
43 #define VIDEO_DRV_INIT /* nothing */
44 #define VIDEO_DRV_WINDOW the_win
45 #define VIDEO_DRV_GC the_gc
46 #define VIDEO_DRV_IMAGE img
47 #define VIDEO_DRV_HAVE_SHM have_shm
48 #else
49 #define MONITOR_INIT X11_monitor_desc &monitor
50 #define VIDEO_DRV_INIT driver_window *drv
51 #define VIDEO_DRV_WINDOW drv->w
52 #define VIDEO_DRV_GC drv->gc
53 #define VIDEO_DRV_IMAGE drv->img
54 #define VIDEO_DRV_HAVE_SHM drv->have_shm
55 #endif
56 #define VIDEO_DRV_LOCK_PIXELS /* nothing */
57 #define VIDEO_DRV_UNLOCK_PIXELS /* nothing */
58 #define VIDEO_DRV_ROW_BYTES VIDEO_DRV_IMAGE->bytes_per_line
59 #endif
60
61 // Variables for Video on SEGV support
62 static uint8 *the_host_buffer; // Host frame buffer in VOSF mode
63
64 struct ScreenPageInfo {
65 int top, bottom; // Mapping between this virtual page and Mac scanlines
66 };
67
68 struct ScreenInfo {
69 uintptr memStart; // Start address aligned to page boundary
70 uint32 memLength; // Length of the memory addressed by the screen pages
71
72 uintptr pageSize; // Size of a page
73 int pageBits; // Shift count to get the page number
74 uint32 pageCount; // Number of pages allocated to the screen
75
76 bool dirty; // Flag: set if the frame buffer was touched
77 char * dirtyPages; // Table of flags set if page was altered
78 ScreenPageInfo * pageInfo; // Table of mappings page -> Mac scanlines
79 };
80
81 static ScreenInfo mainBuffer;
82
83 #define PFLAG_SET_VALUE 0x00
84 #define PFLAG_CLEAR_VALUE 0x01
85 #define PFLAG_SET_VALUE_4 0x00000000
86 #define PFLAG_CLEAR_VALUE_4 0x01010101
87 #define PFLAG_SET(page) mainBuffer.dirtyPages[page] = PFLAG_SET_VALUE
88 #define PFLAG_CLEAR(page) mainBuffer.dirtyPages[page] = PFLAG_CLEAR_VALUE
89 #define PFLAG_ISSET(page) (mainBuffer.dirtyPages[page] == PFLAG_SET_VALUE)
90 #define PFLAG_ISCLEAR(page) (mainBuffer.dirtyPages[page] != PFLAG_SET_VALUE)
91
92 #ifdef UNALIGNED_PROFITABLE
93 # define PFLAG_ISSET_4(page) (*((uint32 *)(mainBuffer.dirtyPages + (page))) == PFLAG_SET_VALUE_4)
94 # define PFLAG_ISCLEAR_4(page) (*((uint32 *)(mainBuffer.dirtyPages + (page))) == PFLAG_CLEAR_VALUE_4)
95 #else
96 # define PFLAG_ISSET_4(page) \
97 PFLAG_ISSET(page ) && PFLAG_ISSET(page+1) \
98 && PFLAG_ISSET(page+2) && PFLAG_ISSET(page+3)
99 # define PFLAG_ISCLEAR_4(page) \
100 PFLAG_ISCLEAR(page ) && PFLAG_ISCLEAR(page+1) \
101 && PFLAG_ISCLEAR(page+2) && PFLAG_ISCLEAR(page+3)
102 #endif
103
104 // Set the selected page range [ first_page, last_page [ into the SET state
105 #define PFLAG_SET_RANGE(first_page, last_page) \
106 memset(mainBuffer.dirtyPages + (first_page), PFLAG_SET_VALUE, \
107 (last_page) - (first_page))
108
109 // Set the selected page range [ first_page, last_page [ into the CLEAR state
110 #define PFLAG_CLEAR_RANGE(first_page, last_page) \
111 memset(mainBuffer.dirtyPages + (first_page), PFLAG_CLEAR_VALUE, \
112 (last_page) - (first_page))
113
114 #define PFLAG_SET_ALL do { \
115 PFLAG_SET_RANGE(0, mainBuffer.pageCount); \
116 mainBuffer.dirty = true; \
117 } while (0)
118
119 #define PFLAG_CLEAR_ALL do { \
120 PFLAG_CLEAR_RANGE(0, mainBuffer.pageCount); \
121 mainBuffer.dirty = false; \
122 } while (0)
123
124 // Set the following macro definition to 1 if your system
125 // provides a really fast strchr() implementation
126 //#define HAVE_FAST_STRCHR 0
127
128 static inline int find_next_page_set(int page)
129 {
130 #if HAVE_FAST_STRCHR
131 char *match = strchr(mainBuffer.dirtyPages + page, PFLAG_SET_VALUE);
132 return match ? match - mainBuffer.dirtyPages : mainBuffer.pageCount;
133 #else
134 while (PFLAG_ISCLEAR_4(page))
135 page += 4;
136 while (PFLAG_ISCLEAR(page))
137 page++;
138 return page;
139 #endif
140 }
141
142 static inline int find_next_page_clear(int page)
143 {
144 #if HAVE_FAST_STRCHR
145 char *match = strchr(mainBuffer.dirtyPages + page, PFLAG_CLEAR_VALUE);
146 return match ? match - mainBuffer.dirtyPages : mainBuffer.pageCount;
147 #else
148 while (PFLAG_ISSET_4(page))
149 page += 4;
150 while (PFLAG_ISSET(page))
151 page++;
152 return page;
153 #endif
154 }
155
156 #ifdef HAVE_SPINLOCKS
157 static spinlock_t vosf_lock = SPIN_LOCK_UNLOCKED; // Mutex to protect frame buffer (dirtyPages in fact)
158 #define LOCK_VOSF spin_lock(&vosf_lock)
159 #define UNLOCK_VOSF spin_unlock(&vosf_lock)
160 #elif defined(_WIN32)
161 static mutex_t vosf_lock; // Mutex to protect frame buffer (dirtyPages in fact)
162 #define LOCK_VOSF vosf_lock.lock();
163 #define UNLOCK_VOSF vosf_lock.unlock();
164 #elif defined(HAVE_PTHREADS)
165 static pthread_mutex_t vosf_lock = PTHREAD_MUTEX_INITIALIZER; // Mutex to protect frame buffer (dirtyPages in fact)
166 #define LOCK_VOSF pthread_mutex_lock(&vosf_lock);
167 #define UNLOCK_VOSF pthread_mutex_unlock(&vosf_lock);
168 #else
169 #define LOCK_VOSF
170 #define UNLOCK_VOSF
171 #endif
172
173 static int log_base_2(uint32 x)
174 {
175 uint32 mask = 0x80000000;
176 int l = 31;
177 while (l >= 0 && (x & mask) == 0) {
178 mask >>= 1;
179 l--;
180 }
181 return l;
182 }
183
184 // Extend size to page boundary
185 static uint32 page_extend(uint32 size)
186 {
187 const uint32 page_size = vm_get_page_size();
188 const uint32 page_mask = page_size - 1;
189 return (size + page_mask) & ~page_mask;
190 }
191
192
193 /*
194 * Check if VOSF acceleration is profitable on this platform
195 */
196
197 const int VOSF_PROFITABLE_TRIES = 3; // Make 3 attempts for full screen update
198 const int VOSF_PROFITABLE_THRESHOLD = 16667; // 60 Hz
199
200 static bool video_vosf_profitable(void)
201 {
202 int64 durations[VOSF_PROFITABLE_TRIES];
203 int mean_duration = 0;
204
205 for (int i = 0; i < VOSF_PROFITABLE_TRIES; i++) {
206 uint64 start = GetTicks_usec();
207 for (int p = 0; p < mainBuffer.pageCount; p++) {
208 uint8 *addr = (uint8 *)(mainBuffer.memStart + (p * mainBuffer.pageSize));
209 addr[0] = 0; // Trigger Screen_fault_handler()
210 }
211 int64 duration = GetTicks_usec() - start;
212 mean_duration += duration;
213 durations[i] = duration;
214
215 PFLAG_CLEAR_ALL;
216 mainBuffer.dirty = false;
217 if (vm_protect((char *)mainBuffer.memStart, mainBuffer.memLength, VM_PAGE_READ) != 0)
218 return false;
219 }
220
221 mean_duration /= VOSF_PROFITABLE_TRIES;
222 D(bug("Triggered %d screen faults in %ld usec on average\n", mainBuffer.pageCount, mean_duration));
223 return (mean_duration < (VOSF_PROFITABLE_THRESHOLD * (frame_skip ? frame_skip : 1)));
224 }
225
226
227 /*
228 * Initialize the VOSF system (mainBuffer structure, SIGSEGV handler)
229 */
230
231 static bool video_vosf_init(MONITOR_INIT)
232 {
233 VIDEO_MODE_INIT_MONITOR;
234
235 const uintptr page_size = vm_get_page_size();
236 const uintptr page_mask = page_size - 1;
237
238 // Round up frame buffer base to page boundary
239 mainBuffer.memStart = (((uintptr) the_buffer) + page_mask) & ~page_mask;
240
241 // The frame buffer size shall already be aligned to page boundary (use page_extend)
242 mainBuffer.memLength = the_buffer_size;
243
244 mainBuffer.pageSize = page_size;
245 mainBuffer.pageBits = log_base_2(mainBuffer.pageSize);
246 mainBuffer.pageCount = (mainBuffer.memLength + page_mask)/mainBuffer.pageSize;
247
248 // The "2" more bytes requested are a safety net to insure the
249 // loops in the update routines will terminate.
250 // See "How can we deal with array overrun conditions ?" hereunder for further details.
251 mainBuffer.dirtyPages = (char *) malloc(mainBuffer.pageCount + 2);
252 if (mainBuffer.dirtyPages == NULL)
253 return false;
254
255 PFLAG_CLEAR_ALL;
256 PFLAG_CLEAR(mainBuffer.pageCount);
257 PFLAG_SET(mainBuffer.pageCount+1);
258
259 // Allocate and fill in pageInfo with start and end (inclusive) row in number of bytes
260 mainBuffer.pageInfo = (ScreenPageInfo *) malloc(mainBuffer.pageCount * sizeof(ScreenPageInfo));
261 if (mainBuffer.pageInfo == NULL)
262 return false;
263
264 uint32 a = 0;
265 for (unsigned i = 0; i < mainBuffer.pageCount; i++) {
266 unsigned y1 = a / VIDEO_MODE_ROW_BYTES;
267 if (y1 >= VIDEO_MODE_Y)
268 y1 = VIDEO_MODE_Y - 1;
269
270 unsigned y2 = (a + mainBuffer.pageSize) / VIDEO_MODE_ROW_BYTES;
271 if (y2 >= VIDEO_MODE_Y)
272 y2 = VIDEO_MODE_Y - 1;
273
274 mainBuffer.pageInfo[i].top = y1;
275 mainBuffer.pageInfo[i].bottom = y2;
276
277 a += mainBuffer.pageSize;
278 if (a > mainBuffer.memLength)
279 a = mainBuffer.memLength;
280 }
281
282 // We can now write-protect the frame buffer
283 if (vm_protect((char *)mainBuffer.memStart, mainBuffer.memLength, VM_PAGE_READ) != 0)
284 return false;
285
286 // The frame buffer is sane, i.e. there is no write to it yet
287 mainBuffer.dirty = false;
288 return true;
289 }
290
291
292 /*
293 * Deinitialize VOSF system
294 */
295
296 static void video_vosf_exit(void)
297 {
298 if (mainBuffer.pageInfo) {
299 free(mainBuffer.pageInfo);
300 mainBuffer.pageInfo = NULL;
301 }
302 if (mainBuffer.dirtyPages) {
303 free(mainBuffer.dirtyPages);
304 mainBuffer.dirtyPages = NULL;
305 }
306 }
307
308
309 /*
310 * Screen fault handler
311 */
312
313 bool Screen_fault_handler(sigsegv_address_t fault_address, sigsegv_address_t fault_instruction)
314 {
315 const uintptr addr = (uintptr)fault_address;
316
317 /* Someone attempted to write to the frame buffer. Make it writeable
318 * now so that the data could actually be written to. It will be made
319 * read-only back in one of the screen update_*() functions.
320 */
321 if (((uintptr)addr - mainBuffer.memStart) < mainBuffer.memLength) {
322 const int page = ((uintptr)addr - mainBuffer.memStart) >> mainBuffer.pageBits;
323 LOCK_VOSF;
324 PFLAG_SET(page);
325 vm_protect((char *)(addr & -mainBuffer.pageSize), mainBuffer.pageSize, VM_PAGE_READ | VM_PAGE_WRITE);
326 mainBuffer.dirty = true;
327 UNLOCK_VOSF;
328 return true;
329 }
330
331 /* Otherwise, we don't know how to handle the fault, let it crash */
332 return false;
333 }
334
335
336 /*
337 * Update display for Windowed mode and VOSF
338 */
339
340 /* How can we deal with array overrun conditions ?
341
342 The state of the framebuffer pages that have been touched are maintained
343 in the dirtyPages[] table. That table is (pageCount + 2) bytes long.
344
345 Terminology
346
347 "Last Page" denotes the pageCount-nth page, i.e. dirtyPages[pageCount - 1].
348 "CLEAR Page Guard" refers to the page following the Last Page but is always
349 in the CLEAR state. "SET Page Guard" refers to the page following the CLEAR
350 Page Guard but is always in the SET state.
351
352 Rough process
353
354 The update routines must determine which pages have to be blitted to the
355 screen. This job consists in finding the first_page that was touched.
356 i.e. find the next page that is SET. Then, finding how many pages were
357 touched starting from first_page. i.e. find the next page that is CLEAR.
358
359 There are two cases to check:
360
361 - Last Page is CLEAR: find_next_page_set() will reach the SET Page Guard
362 but it is beyond the valid pageCount value. Therefore, we exit from the
363 update routine.
364
365 - Last Page is SET: first_page equals (pageCount - 1) and
366 find_next_page_clear() will reach the CLEAR Page Guard. We blit the last
367 page to the screen. On the next iteration, page equals pageCount and
368 find_next_page_set() will reach the SET Page Guard. We still safely exit
369 from the update routine because the SET Page Guard position is greater
370 than pageCount.
371 */
372
373 static inline void update_display_window_vosf(VIDEO_DRV_INIT)
374 {
375 VIDEO_MODE_INIT;
376
377 int page = 0;
378 for (;;) {
379 const unsigned first_page = find_next_page_set(page);
380 if (first_page >= mainBuffer.pageCount)
381 break;
382
383 page = find_next_page_clear(first_page);
384 PFLAG_CLEAR_RANGE(first_page, page);
385
386 // Make the dirty pages read-only again
387 const int32 offset = first_page << mainBuffer.pageBits;
388 const uint32 length = (page - first_page) << mainBuffer.pageBits;
389 vm_protect((char *)mainBuffer.memStart + offset, length, VM_PAGE_READ);
390
391 // There is at least one line to update
392 const int y1 = mainBuffer.pageInfo[first_page].top;
393 const int y2 = mainBuffer.pageInfo[page - 1].bottom;
394 const int height = y2 - y1 + 1;
395
396 VIDEO_DRV_LOCK_PIXELS;
397
398 if ((int)VIDEO_MODE_DEPTH < VIDEO_DEPTH_8BIT) {
399
400 // Update the_host_buffer and copy of the_buffer
401 const int src_bytes_per_row = VIDEO_MODE_ROW_BYTES;
402 const int dst_bytes_per_row = VIDEO_DRV_ROW_BYTES;
403 const int pixels_per_byte = VIDEO_MODE_X / src_bytes_per_row;
404 int i1 = y1 * src_bytes_per_row, i2 = y1 * dst_bytes_per_row, j;
405 for (j = y1; j <= y2; j++) {
406 Screen_blit(the_host_buffer + i2, the_buffer + i1, VIDEO_MODE_X / pixels_per_byte);
407 i1 += src_bytes_per_row;
408 i2 += dst_bytes_per_row;
409 }
410
411 } else {
412
413 // Update the_host_buffer and copy of the_buffer
414 const int src_bytes_per_row = VIDEO_MODE_ROW_BYTES;
415 const int dst_bytes_per_row = VIDEO_DRV_ROW_BYTES;
416 const int bytes_per_pixel = src_bytes_per_row / VIDEO_MODE_X;
417 int i1 = y1 * src_bytes_per_row, i2 = y1 * dst_bytes_per_row, j;
418 for (j = y1; j <= y2; j++) {
419 Screen_blit(the_host_buffer + i2, the_buffer + i1, bytes_per_pixel * VIDEO_MODE_X);
420 i1 += src_bytes_per_row;
421 i2 += dst_bytes_per_row;
422 }
423 }
424
425 VIDEO_DRV_UNLOCK_PIXELS;
426
427 #ifdef USE_SDL_VIDEO
428 SDL_UpdateRect(drv->s, 0, y1, VIDEO_MODE_X, height);
429 #else
430 if (VIDEO_DRV_HAVE_SHM)
431 XShmPutImage(x_display, VIDEO_DRV_WINDOW, VIDEO_DRV_GC, VIDEO_DRV_IMAGE, 0, y1, 0, y1, VIDEO_MODE_X, height, 0);
432 else
433 XPutImage(x_display, VIDEO_DRV_WINDOW, VIDEO_DRV_GC, VIDEO_DRV_IMAGE, 0, y1, 0, y1, VIDEO_MODE_X, height);
434 #endif
435 }
436 mainBuffer.dirty = false;
437 }
438
439
440 /*
441 * Update display for DGA mode and VOSF
442 * (only in Real or Direct Addressing mode)
443 */
444
445 #if REAL_ADDRESSING || DIRECT_ADDRESSING
446 static inline void update_display_dga_vosf(void)
447 {
448 VIDEO_MODE_INIT;
449
450 int page = 0;
451 for (;;) {
452 const unsigned first_page = find_next_page_set(page);
453 if (first_page >= mainBuffer.pageCount)
454 break;
455
456 page = find_next_page_clear(first_page);
457 PFLAG_CLEAR_RANGE(first_page, page);
458
459 // Make the dirty pages read-only again
460 const int32 offset = first_page << mainBuffer.pageBits;
461 const uint32 length = (page - first_page) << mainBuffer.pageBits;
462 vm_protect((char *)mainBuffer.memStart + offset, length, VM_PAGE_READ);
463
464 // I am sure that y2 >= y1 and depth != 1
465 const int y1 = mainBuffer.pageInfo[first_page].top;
466 const int y2 = mainBuffer.pageInfo[page - 1].bottom;
467
468 const int bytes_per_row = VIDEO_MODE_ROW_BYTES;
469 const int bytes_per_pixel = VIDEO_MODE_ROW_BYTES / VIDEO_MODE_X;
470 int i, j;
471
472 // Check for first column from left and first column
473 // from right that have changed
474 int x1 = VIDEO_MODE_X * bytes_per_pixel - 1;
475 for (j = y1; j <= y2; j++) {
476 uint8 * const p1 = &the_buffer[j * bytes_per_row];
477 uint8 * const p2 = &the_buffer_copy[j * bytes_per_row];
478 for (i = 0; i < x1; i++) {
479 if (p1[i] != p2[i]) {
480 x1 = i;
481 break;
482 }
483 }
484 }
485 x1 /= bytes_per_pixel;
486
487 int x2 = x1 * bytes_per_pixel;
488 for (j = y2; j >= y1; j--) {
489 uint8 * const p1 = &the_buffer[j * bytes_per_row];
490 uint8 * const p2 = &the_buffer_copy[j * bytes_per_row];
491 for (i = VIDEO_MODE_X * bytes_per_pixel - 1; i > x2; i--) {
492 if (p1[i] != p2[i]) {
493 x2 = i;
494 break;
495 }
496 }
497 }
498 x2 /= bytes_per_pixel;
499
500 // Update the_host_buffer and copy of the_buffer
501 // There should be at least one pixel to copy
502 VIDEO_DRV_LOCK_PIXELS;
503 const int width = x2 - x1 + 1;
504 i = y1 * bytes_per_row + x1 * bytes_per_pixel;
505 for (j = y1; j <= y2; j++) {
506 Screen_blit(the_host_buffer + i, the_buffer + i, bytes_per_pixel * width);
507 memcpy(the_buffer_copy + i, the_buffer + i, bytes_per_pixel * width);
508 i += bytes_per_row;
509 }
510 VIDEO_DRV_UNLOCK_PIXELS;
511 }
512 mainBuffer.dirty = false;
513 }
514 #endif
515
516 #endif /* ENABLE_VOSF */
517
518 #endif /* VIDEO_VOSF_H */