ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/cebix/BasiliskII/src/Unix/video_vosf.h
Revision: 1.44
Committed: 2004-11-15T23:27:43Z (19 years, 7 months ago) by gbeauche
Content type: text/plain
Branch: MAIN
Changes since 1.43: +3 -0 lines
Log Message:
Backport copy-paste of 'TEXT' from SheepShaver

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