1/* 2 * SDL - Simple DirectMedia Layer 3 * CELL BE Support for PS3 Framebuffer 4 * Copyright (C) 2008, 2009 International Business Machines Corporation 5 * 6 * This library is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU Lesser General Public License as published 8 * by the Free Software Foundation; either version 2.1 of the License, or 9 * (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, but 12 * WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 19 * USA 20 * 21 * Martin Lowinski <lowinski [at] de [dot] ibm [ibm] com> 22 * Dirk Herrendoerfer <d.herrendoerfer [at] de [dot] ibm [dot] com> 23 * SPE code based on research by: 24 * Rene Becker 25 * Thimo Emmerich 26 */ 27 28#include "SDL_config.h" 29 30#include "SDL_video.h" 31#include "../SDL_sysvideo.h" 32#include "SDL_ps3events_c.h" 33#include "SDL_ps3video.h" 34#include "SDL_ps3yuv_c.h" 35#include "spulibs/spu_common.h" 36 37#include <fcntl.h> 38#include <stdlib.h> 39#include <sys/ioctl.h> 40#include <linux/kd.h> 41#include <sys/mman.h> 42 43#include <linux/fb.h> 44#include <asm/ps3fb.h> 45#include <libspe2.h> 46#include <malloc.h> 47 48/* SDL_VideoDevice functions */ 49static int PS3_Available(); 50static SDL_VideoDevice *PS3_CreateDevice(int devindex); 51static int PS3_VideoInit(_THIS, SDL_PixelFormat * vformat); 52static void PS3_VideoQuit(_THIS); 53static void PS3_DeleteDevice(SDL_VideoDevice * device); 54static SDL_Surface *PS3_SetVideoMode(_THIS, SDL_Surface * current, int width, int height, int bpp, Uint32 flags); 55static SDL_Rect **PS3_ListModes(_THIS, SDL_PixelFormat * format, Uint32 flags); 56 57/* Hardware surface functions */ 58static int PS3_AllocHWSurface(_THIS, SDL_Surface * surface); 59static void PS3_FreeHWSurface(_THIS, SDL_Surface * surface); 60static int PS3_LockHWSurface(_THIS, SDL_Surface * surface); 61static void PS3_UnlockHWSurface(_THIS, SDL_Surface * surface); 62static int PS3_FlipDoubleBuffer(_THIS, SDL_Surface * surface); 63static void PS3_DoubleBufferUpdate(_THIS, int numrects, SDL_Rect * rects); 64 65/* SPU specific functions */ 66int SPE_Start(_THIS, spu_data_t * spe_data); 67int SPE_Stop(_THIS, spu_data_t * spe_data); 68int SPE_Boot(_THIS, spu_data_t * spe_data); 69int SPE_Shutdown(_THIS, spu_data_t * spe_data); 70int SPE_SendMsg(_THIS, spu_data_t * spe_data, unsigned int msg); 71int SPE_WaitForMsg(_THIS, spu_data_t * spe_data, unsigned int msg); 72void SPE_RunContext(void *thread_argp); 73 74/* Helpers */ 75void enable_cursor(int enable); 76 77/* Stores the SPE executable name of fb_writer_spu */ 78extern spe_program_handle_t fb_writer_spu; 79 80/* SDL PS3 bootstrap function for checking availability */ 81static int PS3_Available() 82{ 83 return 1; 84} 85 86/* SDL PS3 bootstrap function for creating the device */ 87static SDL_VideoDevice *PS3_CreateDevice(int devindex) 88{ 89 SDL_VideoDevice *this; 90 91 /* Initialise SDL_VideoDevice */ 92 this = (SDL_VideoDevice *) SDL_malloc(sizeof(SDL_VideoDevice)); 93 if (this) { 94 memset(this, 0, sizeof *this); 95 this->hidden = (struct SDL_PrivateVideoData *) 96 SDL_malloc(sizeof(struct SDL_PrivateVideoData)); 97 } 98 /* Error handling */ 99 if ((this == NULL) || (this->hidden == NULL)) { 100 SDL_OutOfMemory(); 101 if (this) 102 SDL_free(this); 103 return 0; 104 } 105 memset(this->hidden, 0, sizeof(struct SDL_PrivateVideoData)); 106 107 /* Set the function pointers */ 108 this->VideoInit = PS3_VideoInit; 109 this->ListModes = PS3_ListModes; 110 this->SetVideoMode = PS3_SetVideoMode; 111 this->SetColors = 0; 112 this->CreateYUVOverlay = PS3_CreateYUVOverlay; 113 this->UpdateRects = 0; 114 this->VideoQuit = PS3_VideoQuit; 115 this->AllocHWSurface = PS3_AllocHWSurface; 116 this->CheckHWBlit = 0; 117 this->FillHWRect = 0; 118 this->SetHWColorKey = 0; 119 this->SetHWAlpha = 0; 120 this->LockHWSurface = PS3_LockHWSurface; 121 this->UnlockHWSurface = PS3_UnlockHWSurface; 122 this->FlipHWSurface = PS3_FlipDoubleBuffer; 123 this->FreeHWSurface = PS3_FreeHWSurface; 124 this->SetCaption = 0; 125 this->SetIcon = 0; 126 this->IconifyWindow = 0; 127 this->GrabInput = 0; 128 this->GetWMInfo = 0; 129 this->InitOSKeymap = PS3_InitOSKeymap; 130 this->PumpEvents = PS3_PumpEvents; 131 132 this->free = PS3_DeleteDevice; 133 134 return this; 135} 136 137 138/* Bootstraping (see SDL_sysvideo.h) */ 139VideoBootStrap PS3_bootstrap = { 140 "ps3", "PS3 Cell SPU Driver", 141 PS3_Available, PS3_CreateDevice 142}; 143 144 145/* Delete the device */ 146static void PS3_DeleteDevice(SDL_VideoDevice * device) 147{ 148 free(device->hidden); 149 free(device); 150} 151 152 153/* Initialise the PS3 video device */ 154static int PS3_VideoInit(_THIS, SDL_PixelFormat * vformat) 155{ 156 /* Hide the cursor */ 157 enable_cursor(0); 158 159 /* Create SPU fb_parms and thread structure */ 160 fb_parms = (struct fb_writer_parms_t *) 161 memalign(16, sizeof(struct fb_writer_parms_t)); 162 fb_thread_data = (spu_data_t *) malloc(sizeof(spu_data_t)); 163 if (fb_parms == NULL || fb_thread_data == NULL) { 164 SDL_OutOfMemory(); 165 return -1; 166 } 167 fb_thread_data->program = fb_writer_spu; 168 fb_thread_data->program_name = "fb_writer_spu"; 169 fb_thread_data->argp = (void *)fb_parms; 170 fb_thread_data->keepalive = 1; 171 fb_thread_data->booted = 0; 172 173 SPE_Start(this, fb_thread_data); 174 175 /* Open the device */ 176 fb_dev_fd = open(PS3_DEV_FB, O_RDWR); 177 if (fb_dev_fd < 0) { 178 SDL_SetError("[PS3] Unable to open device %s", PS3_DEV_FB); 179 return -1; 180 } 181 182 /* Get vscreeninfo */ 183 if (ioctl(fb_dev_fd, FBIOGET_VSCREENINFO, &fb_vinfo)) { 184 SDL_SetError("[PS3] Can't get VSCREENINFO"); 185 if (fb_dev_fd >= 0) 186 close(fb_dev_fd); 187 fb_dev_fd = -1; 188 return -1; 189 } 190 191 /* Fill in our hardware acceleration capabilities */ 192 this->info.current_w = fb_vinfo.xres; 193 this->info.current_h = fb_vinfo.yres; 194 this->info.wm_available = 0; 195 this->info.hw_available = 1; 196 197 /* Backup the original vinfo to restore later */ 198 fb_orig_vinfo = fb_vinfo; 199 200 /* 16 and 15 bpp is reported as 16 bpp */ 201 fb_bits_per_pixel = fb_vinfo.bits_per_pixel; 202 if (fb_bits_per_pixel == 16) 203 fb_bits_per_pixel = 204 fb_vinfo.red.length + fb_vinfo.green.length + 205 fb_vinfo.blue.length; 206 207 /* Set SDL_PixelFormat */ 208 vformat->BitsPerPixel = fb_vinfo.bits_per_pixel; 209 210 fb_vinfo.xres_virtual = fb_vinfo.xres; 211 fb_vinfo.yres_virtual = fb_vinfo.yres; 212 213 /* Put vscreeninfo */ 214 if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_vinfo)) { 215 SDL_SetError("[PS3] Can't put VSCREENINFO"); 216 if (fb_dev_fd >= 0) 217 close(fb_dev_fd); 218 fb_dev_fd = -1; 219 return -1; 220 } 221 222 s_fb_pixel_size = fb_vinfo.bits_per_pixel / 8; 223 224 s_writeable_width = fb_vinfo.xres; 225 s_writeable_height = fb_vinfo.yres; 226 227 /* Get ps3 screeninfo */ 228 if (ioctl(fb_dev_fd, PS3FB_IOCTL_SCREENINFO, (unsigned long)&res) < 0) { 229 SDL_SetError("[PS3] PS3FB_IOCTL_SCREENINFO failed"); 230 } 231 deprintf(1, "[PS3] xres:%d yres:%d xoff:%d yoff:%d\n", res.xres, res.yres, res.xoff, res.yoff); 232 233 /* Only use double buffering if enough fb memory is available */ 234 if (res.num_frames < 2) { 235 double_buffering = 0; 236 } else { 237 double_buffering = 1; 238 } 239 240 real_width = res.xres; 241 real_height = res.yres; 242 243 /* 244 * Take control of frame buffer from kernel, for details see 245 * http://felter.org/wesley/files/ps3/linux-20061110-docs/ApplicationProgrammingEnvironment.html 246 * kernel will no longer flip the screen itself 247 */ 248 ioctl(fb_dev_fd, PS3FB_IOCTL_ON, 0); 249 250 /* Unblank screen */ 251 ioctl(fb_dev_fd, FBIOBLANK, 0); 252 253 return 0; 254} 255 256 257/* List available PS3 resolutions */ 258static SDL_Rect **PS3_ListModes(_THIS, SDL_PixelFormat * format, Uint32 flags) 259{ 260 /* A list of video resolutions that we query for (sorted largest to 261 * smallest) 262 */ 263 static SDL_Rect PS3_resolutions[] = { 264 {0, 0, 1920, 1080}, // 1080p 16:9 HD 265 {0, 0, 1600, 1200}, // WUXGA 266 {0, 0, 1280, 1024}, // SXGA 267 {0, 0, 1280, 720}, // 720p 16:9 HD 268 {0, 0, 1024, 768}, // WXGA 269 {0, 0, 1024, 576}, // 576p 16:9 270 {0, 0, 853, 480}, // 480p 16:9 271 {0, 0, 720, 576}, // 576p 4:3 (PAL) 272 {0, 0, 720, 480}, // 480p 16:9 (NTSC) 273 }; 274 static SDL_Rect *PS3_modes[] = { 275 &PS3_resolutions[0], 276 &PS3_resolutions[1], 277 &PS3_resolutions[2], 278 &PS3_resolutions[3], 279 &PS3_resolutions[4], 280 &PS3_resolutions[5], 281 &PS3_resolutions[6], 282 &PS3_resolutions[7], 283 &PS3_resolutions[8], 284 NULL 285 }; 286 SDL_Rect **modes = PS3_modes; 287 288 return modes; 289} 290 291 292/* Get a list of the available display modes */ 293static SDL_Surface *PS3_SetVideoMode(_THIS, SDL_Surface * current, int width, int height, int bpp, Uint32 flags) 294{ 295 s_bounded_input_width = width < s_writeable_width ? width : s_writeable_width; 296 s_bounded_input_height = height < s_writeable_height ? height : s_writeable_height; 297 s_bounded_input_width_offset = (s_writeable_width - s_bounded_input_width) >> 1; 298 s_bounded_input_height_offset = (s_writeable_height - s_bounded_input_height) >> 1; 299 s_input_line_length = width * s_fb_pixel_size; 300 301 current->flags |= flags; 302 303 if (ioctl(fb_dev_fd, FBIOGET_FSCREENINFO, &fb_finfo)) { 304 SDL_SetError("[PS3] Can't get fixed screeninfo"); 305 return NULL; 306 } 307 308 if (fb_finfo.type != FB_TYPE_PACKED_PIXELS) { 309 SDL_SetError("[PS3] type %s not supported", 310 fb_finfo.type); 311 return NULL; 312 } 313 314 /* Note: on PS3, fb_finfo.smem_len is enough for double buffering */ 315 if ((frame_buffer = 316 (uint8_t *) mmap(0, fb_finfo.smem_len, 317 PROT_READ | PROT_WRITE, MAP_SHARED, 318 fb_dev_fd, 0)) == (uint8_t *) - 1) { 319 SDL_SetError("[PS3] Can't mmap for %s", PS3_DEV_FB); 320 return NULL; 321 } else { 322 current->flags |= SDL_DOUBLEBUF; 323 } 324 if (!SDL_ReallocFormat(current, fb_bits_per_pixel, 0, 0, 0, 0)) { 325 return (NULL); 326 } 327 328 /* Blank screen */ 329 memset(frame_buffer, 0x00, fb_finfo.smem_len); 330 331 /* Centering */ 332 s_center[0] = 333 frame_buffer + s_bounded_input_width_offset * s_fb_pixel_size + 334 s_bounded_input_height_offset * fb_finfo.line_length; 335 s_center[1] = s_center[0] + real_height * fb_finfo.line_length; 336 s_center_index = 0; 337 338 current->flags |= SDL_FULLSCREEN; 339 current->w = width; 340 current->h = height; 341 current->pitch = SDL_CalculatePitch(current); 342 343 /* Alloc aligned mem for current->pixels */ 344 s_pixels = memalign(16, current->h * current->pitch); 345 current->pixels = (void *)s_pixels; 346 if (!current->pixels) { 347 SDL_OutOfMemory(); 348 return NULL; 349 } 350 351 /* Set the update rectangle function */ 352 this->UpdateRects = PS3_DoubleBufferUpdate; 353 354 return current; 355} 356 357 358/* Copy screen to framebuffer and flip */ 359void PS3_DoubleBufferUpdate(_THIS, int numrects, SDL_Rect * rects) 360{ 361 if (converter_thread_data && converter_thread_data->booted) 362 SPE_WaitForMsg(this, converter_thread_data, SPU_FIN); 363 364 /* Adjust centering */ 365 s_bounded_input_width_offset = (s_writeable_width - s_bounded_input_width) >> 1; 366 s_bounded_input_height_offset = (s_writeable_height - s_bounded_input_height) >> 1; 367 s_center[0] = frame_buffer + s_bounded_input_width_offset * s_fb_pixel_size + 368 s_bounded_input_height_offset * fb_finfo.line_length; 369 s_center[1] = s_center[0] + real_height * fb_finfo.line_length; 370 371 /* Set SPU parms for copying the surface to framebuffer */ 372 fb_parms->data = (unsigned char *)s_pixels; 373 fb_parms->center = s_center[s_center_index]; 374 fb_parms->out_line_stride = fb_finfo.line_length; 375 fb_parms->in_line_stride = s_input_line_length; 376 fb_parms->bounded_input_height = s_bounded_input_height; 377 fb_parms->bounded_input_width = s_bounded_input_width; 378 fb_parms->fb_pixel_size = s_fb_pixel_size; 379 380 deprintf(3, "[PS3->SPU] fb_thread_data->argp = 0x%x\n", fb_thread_data->argp); 381 382 /* Copying.. */ 383 SPE_SendMsg(this, fb_thread_data, SPU_START); 384 SPE_SendMsg(this, fb_thread_data, (unsigned int)fb_thread_data->argp); 385 386 SPE_WaitForMsg(this, fb_thread_data, SPU_FIN); 387 388 /* Flip the pages */ 389 if (double_buffering) 390 s_center_index = s_center_index ^ 0x01; 391 PS3_FlipDoubleBuffer(this, this->screen); 392} 393 394 395/* Enable/Disable cursor */ 396void enable_cursor(int enable) 397{ 398 int fd = open("/dev/console", O_RDWR | O_NONBLOCK); 399 if (fd >= 0) { 400 ioctl(fd, KDSETMODE, enable ? KD_TEXT : KD_GRAPHICS); 401 close(fd); 402 } 403} 404 405 406static int PS3_AllocHWSurface(_THIS, SDL_Surface * surface) 407{ 408 return -1; 409} 410 411 412static void PS3_FreeHWSurface(_THIS, SDL_Surface * surface) 413{ 414 return; 415} 416 417 418static int PS3_LockHWSurface(_THIS, SDL_Surface * surface) 419{ 420 return 0; 421} 422 423 424static void PS3_UnlockHWSurface(_THIS, SDL_Surface * surface) 425{ 426 return; 427} 428 429 430/* Blit/Flip buffer to the screen. Must be called after each frame! */ 431int PS3_FlipDoubleBuffer(_THIS, SDL_Surface * surface) 432{ 433 unsigned long crt = 0; 434 /* Wait for vsync */ 435 deprintf(1, "[PS3] Wait for vsync\n"); 436 ioctl(fb_dev_fd, FBIO_WAITFORVSYNC, &crt); 437 /* Page flip */ 438 deprintf(1, "[PS3] Page flip to buffer #%u 0x%x\n", s_center_index, s_center[s_center_index]); 439 ioctl(fb_dev_fd, PS3FB_IOCTL_FSEL, (unsigned long)&s_center_index); 440 return 1; 441} 442 443 444/* Start the SPE thread */ 445int SPE_Start(_THIS, spu_data_t * spe_data) 446{ 447 deprintf(2, "[PS3->SPU] Start SPE: %s\n", spe_data->program_name); 448 if (!(spe_data->booted)) 449 SPE_Boot(this, spe_data); 450 451 /* To allow re-running of context, spe_ctx_entry has to be set before each call */ 452 spe_data->entry = SPE_DEFAULT_ENTRY; 453 spe_data->error_code = 0; 454 455 /* Create SPE thread and run */ 456 deprintf(2, "[PS3->SPU] Create Thread: %s\n", spe_data->program_name); 457 if (pthread_create 458 (&spe_data->thread, NULL, (void *)&SPE_RunContext, (void *)spe_data)) { 459 deprintf(2, "[PS3->SPU] Could not create pthread for spe: %s\n", spe_data->program_name); 460 SDL_SetError("[PS3->SPU] Could not create pthread for spe"); 461 return -1; 462 } 463 464 if (spe_data->keepalive) 465 SPE_WaitForMsg(this, spe_data, SPU_READY); 466} 467 468 469/* Stop the SPE thread */ 470int SPE_Stop(_THIS, spu_data_t * spe_data) 471{ 472 deprintf(2, "[PS3->SPU] Stop SPE: %s\n", spe_data->program_name); 473 /* Wait for SPE thread to complete */ 474 deprintf(2, "[PS3->SPU] Wait for SPE thread to complete: %s\n", spe_data->program_name); 475 if (pthread_join(spe_data->thread, NULL)) { 476 deprintf(2, "[PS3->SPU] Failed joining the thread: %s\n", spe_data->program_name); 477 SDL_SetError("[PS3->SPU] Failed joining the thread"); 478 return -1; 479 } 480 481 return 0; 482} 483 484 485/* Create SPE context and load program */ 486int SPE_Boot(_THIS, spu_data_t * spe_data) 487{ 488 /* Create SPE context */ 489 deprintf(2, "[PS3->SPU] Create SPE Context: %s\n", spe_data->program_name); 490 spe_data->ctx = spe_context_create(0, NULL); 491 if (spe_data->ctx == NULL) { 492 deprintf(2, "[PS3->SPU] Failed creating SPE context: %s\n", spe_data->program_name); 493 SDL_SetError("[PS3->SPU] Failed creating SPE context"); 494 return -1; 495 } 496 497 /* Load SPE object into SPE local store */ 498 deprintf(2, "[PS3->SPU] Load Program into SPE: %s\n", spe_data->program_name); 499 if (spe_program_load(spe_data->ctx, &spe_data->program)) { 500 deprintf(2, "[PS3->SPU] Failed loading program into SPE context: %s\n", spe_data->program_name); 501 SDL_SetError 502 ("[PS3->SPU] Failed loading program into SPE context"); 503 return -1; 504 } 505 spe_data->booted = 1; 506 deprintf(2, "[PS3->SPU] SPE boot successful\n"); 507 508 return 0; 509} 510 511/* (Stop and) shutdown the SPE */ 512int SPE_Shutdown(_THIS, spu_data_t * spe_data) 513{ 514 if (spe_data->keepalive && spe_data->booted) { 515 SPE_SendMsg(this, spe_data, SPU_EXIT); 516 SPE_Stop(this, spe_data); 517 } 518 519 /* Destroy SPE context */ 520 deprintf(2, "[PS3->SPU] Destroy SPE context: %s\n", spe_data->program_name); 521 if (spe_context_destroy(spe_data->ctx)) { 522 deprintf(2, "[PS3->SPU] Failed destroying context: %s\n", spe_data->program_name); 523 SDL_SetError("[PS3->SPU] Failed destroying context"); 524 return -1; 525 } 526 deprintf(2, "[PS3->SPU] SPE shutdown successful: %s\n", spe_data->program_name); 527 return 0; 528} 529 530 531/* Send message to the SPE via mailboxe */ 532int SPE_SendMsg(_THIS, spu_data_t * spe_data, unsigned int msg) 533{ 534 deprintf(2, "[PS3->SPU] Sending message %u to %s\n", msg, spe_data->program_name); 535 /* Send one message, block until message was sent */ 536 unsigned int spe_in_mbox_msgs[1]; 537 spe_in_mbox_msgs[0] = msg; 538 int in_mbox_write = spe_in_mbox_write(spe_data->ctx, spe_in_mbox_msgs, 1, SPE_MBOX_ALL_BLOCKING); 539 540 if (1 > in_mbox_write) { 541 deprintf(2, "[PS3->SPU] No message could be written to %s\n", spe_data->program_name); 542 SDL_SetError("[PS3->SPU] No message could be written"); 543 return -1; 544 } 545 return 0; 546} 547 548 549/* Read 1 message from SPE, block until at least 1 message was received */ 550int SPE_WaitForMsg(_THIS, spu_data_t * spe_data, unsigned int msg) 551{ 552 deprintf(2, "[PS3->SPU] Waiting for message from %s\n", spe_data->program_name); 553 unsigned int out_messages[1]; 554 while (!spe_out_mbox_status(spe_data->ctx)); 555 int mbox_read = spe_out_mbox_read(spe_data->ctx, out_messages, 1); 556 deprintf(2, "[PS3->SPU] Got message from %s, message was %u\n", spe_data->program_name, out_messages[0]); 557 if (out_messages[0] == msg) 558 return 0; 559 else 560 return -1; 561} 562 563 564/* Re-runnable invocation of the spe_context_run call */ 565void SPE_RunContext(void *thread_argp) 566{ 567 /* argp is the pointer to argument to be passed to the SPE program */ 568 spu_data_t *args = (spu_data_t *) thread_argp; 569 deprintf(3, "[PS3->SPU] void* argp=0x%x\n", (unsigned int)args->argp); 570 571 /* Run it.. */ 572 deprintf(2, "[PS3->SPU] Run SPE program: %s\n", args->program_name); 573 if (spe_context_run 574 (args->ctx, &args->entry, 0, (void *)args->argp, NULL, 575 NULL) < 0) { 576 deprintf(2, "[PS3->SPU] Failed running SPE context: %s\n", args->program_name); 577 SDL_SetError("[PS3->SPU] Failed running SPE context: %s", args->program_name); 578 exit(1); 579 } 580 581 pthread_exit(NULL); 582} 583 584 585/* Quits the video driver */ 586static void PS3_VideoQuit(_THIS) 587{ 588 if (fb_dev_fd > 0) { 589 /* Restore the original video mode */ 590 if (ioctl(fb_dev_fd, FBIOPUT_VSCREENINFO, &fb_orig_vinfo)) 591 SDL_SetError("[PS3] Can't restore original fb_var_screeninfo"); 592 593 /* Give control of frame buffer to kernel */ 594 ioctl(fb_dev_fd, PS3FB_IOCTL_OFF, 0); 595 close(fb_dev_fd); 596 fb_dev_fd = -1; 597 } 598 599 if (frame_buffer) { 600 munmap(frame_buffer, fb_finfo.smem_len); 601 frame_buffer = 0; 602 } 603 604 if (fb_parms) 605 free((void *)fb_parms); 606 if (fb_thread_data) { 607 SPE_Shutdown(this, fb_thread_data); 608 free((void *)fb_thread_data); 609 } 610 611 if (this->screen) { 612 if (double_buffering && this->screen->pixels) { 613 free(this->screen->pixels); 614 } 615 this->screen->pixels = NULL; 616 } 617 618 enable_cursor(1); 619 deprintf(1, "[PS3] VideoQuit\n"); 620} 621 622