/* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2014 - Daniel De Matteis * * RetroArch is free software: you can redistribute it and/or modify it under the terms * of the GNU General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with RetroArch. * If not, see . */ #include #include #include #include #include #include #include "../psp/sdk_defines.h" #include "../general.h" #include "../driver.h" #include "../gfx/gfx_common.h" #ifndef SCEGU_SCR_WIDTH #define SCEGU_SCR_WIDTH 480 #endif #ifndef SCEGU_SCR_HEIGHT #define SCEGU_SCR_HEIGHT 272 #endif #ifndef SCEGU_VRAM_WIDTH #define SCEGU_VRAM_WIDTH 512 #endif /* Frame buffer */ #define SCEGU_VRAM_TOP (0x44000000) /* 16bit mode */ #define SCEGU_VRAM_BUFSIZE (SCEGU_VRAM_WIDTH*SCEGU_SCR_HEIGHT*2) #define SCEGU_VRAM_BP_0 ((void *)(SCEGU_VRAM_TOP)) #define SCEGU_VRAM_BP_1 ((void *)(SCEGU_VRAM_TOP+SCEGU_VRAM_BUFSIZE)) #define SCEGU_VRAM_BP_2 ((void *)(SCEGU_VRAM_TOP+(SCEGU_VRAM_BUFSIZE*2))) /* 32bit mode */ #define SCEGU_VRAM_BUFSIZE32 (SCEGU_VRAM_WIDTH*SCEGU_SCR_HEIGHT*4) #define SCEGU_VRAM_BP32_0 ((void *)(SCEGU_VRAM_TOP)) #define SCEGU_VRAM_BP32_1 ((void *)(SCEGU_VRAM_TOP+SCEGU_VRAM_BUFSIZE32)) #define SCEGU_VRAM_BP32_2 ((void *)(SCEGU_VRAM_TOP+(SCEGU_VRAM_BUFSIZE32*2))) #define TO_UNCACHED_PTR(ptr) ((void *)((uint32_t)(ptr)|0x40000000)) #define TO_CACHED_PTR(ptr) ((void *)((uint32_t)(ptr)&~0x40000000)) #define FROM_GU_POINTER(ptr) ((void *)((uint32_t)(ptr)|0x44000000)) #define TO_GU_POINTER(ptr) ((void *)((uint32_t)(ptr)&~0x44000000)) typedef struct __attribute__((packed)) psp1_vertex { int16_t u,v; uint16_t color; int16_t x,y,z; } psp1_vertex_t; typedef struct __attribute__((packed)) psp1_sprite { psp1_vertex_t v0; psp1_vertex_t v1; } psp1_sprite_t; typedef struct psp1_menu_frame { void* dList; void* frame; psp1_sprite_t* frame_coords; bool active; PspGeContext context_storage; } psp1_menu_frame_t; typedef struct psp1_video { void* main_dList; void* frame_dList; void* draw_buffer; void* texture; psp1_sprite_t* frame_coords; int tex_filter; bool vsync; bool rgb32; int bpp_log2; psp1_menu_frame_t menu; rarch_viewport_t vp; unsigned rotation; bool vblank_not_reached; bool keep_aspect; bool should_resize; } psp1_video_t; static void psp_update_frame_coords(psp1_video_t* psp); static void psp_update_viewport(psp1_video_t* psp); static void psp_on_vblank(u32 sub, psp1_video_t *psp) { psp->vblank_not_reached = false; } static void *psp_init(const video_info_t *video, const input_driver_t **input, void **input_data) { // to-do : add ASSERT() checks or use main RAM if VRAM is too low for desired video->input_scale void *pspinput; int pixel_format, lut_pixel_format, lut_block_count; unsigned int red_shift, color_mask; void *displayBuffer, *LUT_r, *LUT_b; psp1_video_t *psp = (psp1_video_t*)calloc(1, sizeof(psp1_video_t)); if (!psp) return NULL; sceGuInit(); psp->vp.x = 0; psp->vp.y = 0; psp->vp.width = SCEGU_SCR_WIDTH; psp->vp.height = SCEGU_SCR_HEIGHT; psp->vp.full_width = SCEGU_SCR_WIDTH; psp->vp.full_height = SCEGU_SCR_HEIGHT; // make sure anything using uncached pointers reserves whole cachelines (memory address and size need to be a multiple of 64) // so it isn't overwritten by an unlucky cache writeback. // this includes display lists since the Gu library uses unchached pointers to write to them. psp->main_dList = memalign(64, 256); // allocate more space if bigger display lists are needed. psp->frame_dList = memalign(64, 256); psp->menu.dList = memalign(64, 256); psp->menu.frame = memalign(16, 2 * 480 * 272); psp->frame_coords = memalign(64, 16 * sizeof(psp1_sprite_t)); psp->menu.frame_coords = memalign(64, 16 * sizeof(psp1_sprite_t)); memset(psp->frame_coords , 0, 16 * sizeof(psp1_sprite_t)); memset(psp->menu.frame_coords , 0, 16 * sizeof(psp1_sprite_t)); sceKernelDcacheWritebackInvalidateAll(); psp->frame_coords = TO_UNCACHED_PTR(psp->frame_coords); psp->menu.frame_coords = TO_UNCACHED_PTR(psp->menu.frame_coords);; psp->frame_coords->v0.x = 60; psp->frame_coords->v0.y = 0; psp->frame_coords->v0.u = 0; psp->frame_coords->v0.v = 0; psp->frame_coords->v1.x = 420; psp->frame_coords->v1.y = SCEGU_SCR_HEIGHT; psp->frame_coords->v1.u = 256; psp->frame_coords->v1.v = 240; psp->vsync = video->vsync; psp->rgb32 = video->rgb32; if(psp->rgb32) { uint32_t* LUT_r_local = (uint32_t*)(SCEGU_VRAM_BP32_2); uint32_t* LUT_b_local = (uint32_t*)(SCEGU_VRAM_BP32_2) + (1 << 8); red_shift = 8 + 8; color_mask = 0xFF; lut_block_count = (1 << 8) / 8; psp->texture = (void*)(LUT_b_local + (1 << 8)); psp->draw_buffer = SCEGU_VRAM_BP32_0; psp->bpp_log2 = 2; pixel_format = GU_PSM_8888; lut_pixel_format = GU_PSM_T32; displayBuffer = SCEGU_VRAM_BP32_1; for (u32 i=0; i < (1 << 8); i++){ LUT_r_local[i]= i; LUT_b_local[i]= i << (8 + 8); } LUT_r = (void*)LUT_r_local; LUT_b = (void*)LUT_b_local; } else { uint16_t* LUT_r_local = (uint16_t*)(SCEGU_VRAM_BP_2); uint16_t* LUT_b_local = (uint16_t*)(SCEGU_VRAM_BP_2) + (1 << 5); red_shift = 6 + 5; color_mask = 0x1F; lut_block_count = (1 << 5) / 8; psp->texture = (void*)(LUT_b_local + (1 << 5)); psp->draw_buffer = SCEGU_VRAM_BP_0; psp->bpp_log2 = 1; pixel_format = (g_extern.system.pix_fmt == RETRO_PIXEL_FORMAT_0RGB1555)? GU_PSM_5551 : GU_PSM_5650 ; lut_pixel_format = GU_PSM_T16; displayBuffer = SCEGU_VRAM_BP_1; for (u16 i = 0; i < (1 << 5); i++){ LUT_r_local[i]= i; LUT_b_local[i]= i << (5 + 6); } LUT_r = (void*)LUT_r_local; LUT_b = (void*)LUT_b_local; } psp->tex_filter = video->smooth? GU_LINEAR : GU_NEAREST; sceDisplayWaitVblankStart(); // TODO : check if necessary sceGuDisplay(GU_FALSE); sceGuStart(GU_DIRECT, psp->main_dList); sceGuDrawBuffer(pixel_format, TO_GU_POINTER(psp->draw_buffer), SCEGU_VRAM_WIDTH); sceGuDispBuffer(SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT, TO_GU_POINTER(displayBuffer), SCEGU_VRAM_WIDTH); sceGuClearColor(0); sceGuScissor(0, 0, SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT); sceGuEnable(GU_SCISSOR_TEST); sceGuTexFilter(psp->tex_filter, psp->tex_filter); sceGuTexWrap (GU_CLAMP, GU_CLAMP); sceGuEnable(GU_TEXTURE_2D); sceGuDisable(GU_DEPTH_TEST); sceGuCallMode(GU_FALSE); sceGuFinish(); sceGuSync(0, 0); sceDisplayWaitVblankStart(); // TODO : check if necessary sceGuDisplay(GU_TRUE); pspDebugScreenSetColorMode(pixel_format); pspDebugScreenSetBase(psp->draw_buffer); // fill frame_dList : sceGuStart(GU_CALL, psp->frame_dList); sceGuTexMode(pixel_format, 0, 0, GU_FALSE); sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB); sceGuEnable(GU_BLEND); sceGuBlendFunc(GU_ADD, GU_FIX, GU_FIX, 0x0000FF00, 0xFFFFFFFF); // green only sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_COLOR_4444 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, NULL, (void*)(psp->frame_coords)); sceGuBlendFunc(GU_ADD, GU_FIX, GU_FIX, 0xFFFFFFFF, 0xFFFFFFFF); // restore sceGuTexMode(lut_pixel_format, 0, 0, GU_FALSE); sceGuClutMode(pixel_format, red_shift, color_mask, 0); sceGuClutLoad(lut_block_count, LUT_r); sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_COLOR_4444 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, NULL, (void*)(psp->frame_coords)); sceGuClutMode(pixel_format, 0, color_mask, 0); sceGuClutLoad(lut_block_count, LUT_b); sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_COLOR_4444 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, NULL, (void*)(psp->frame_coords)); sceGuFinish(); if (input && input_data) { pspinput = input_psp.init(); *input = pspinput ? &input_psp : NULL; *input_data = pspinput; } psp->vblank_not_reached = true; sceKernelRegisterSubIntrHandler(PSP_VBLANK_INT, 0, psp_on_vblank, psp); sceKernelEnableSubIntr(PSP_VBLANK_INT, 0); psp->keep_aspect = true; psp->should_resize = true; return psp; error: RARCH_ERR("PSP1 video could not be initialized.\n"); return (void*)-1; } #define DISPLAY_FPS static bool psp_frame(void *data, const void *frame, unsigned width, unsigned height, unsigned pitch, const char *msg) { static char fps_txt[128], fps_text_buf[128]; psp1_video_t *psp = (psp1_video_t*)data; #ifdef DISPLAY_FPS static uint64_t currentTick,lastTick; static float fps=0.0; static int frames; #endif if (!width || !height) return false; if (!(((uint32_t)frame&0x04000000) || (frame == RETRO_HW_FRAME_BUFFER_VALID))) // let the core decide when to sync when HW_RENDER sceGuSync(0, 0); pspDebugScreenSetBase(psp->draw_buffer); pspDebugScreenSetXY(0,0); if(g_settings.fps_show) { gfx_get_fps(fps_txt, sizeof(fps_txt), fps_text_buf, sizeof(fps_text_buf)); pspDebugScreenSetXY(68 - strlen(fps_text_buf) - 1,0); pspDebugScreenPuts(fps_text_buf); pspDebugScreenSetXY(0,1); } else gfx_get_fps(fps_txt, sizeof(fps_txt), NULL, 0); if (msg) pspDebugScreenPuts(msg); if ((psp->vsync)&&(psp->vblank_not_reached)) sceDisplayWaitVblankStart(); psp->vblank_not_reached = true; RARCH_PERFORMANCE_INIT(psp_frame_run); RARCH_PERFORMANCE_START(psp_frame_run); #ifdef DISPLAY_FPS frames++; sceRtcGetCurrentTick(¤tTick); uint32_t diff = currentTick - lastTick; if(diff > 1000000) { fps = (float)frames * 1000000.0 / diff; lastTick = currentTick; frames = 0; } pspDebugScreenSetXY(0,0); pspDebugScreenPrintf("%f", fps); #endif psp->draw_buffer = FROM_GU_POINTER(sceGuSwapBuffers()); g_extern.frame_count++; if (psp->should_resize) psp_update_viewport(psp); // psp->frame_coords->v0.u = 0; // psp->frame_coords->v0.v = 0; psp->frame_coords->v1.u = width; psp->frame_coords->v1.v = height; sceGuStart(GU_DIRECT, psp->main_dList); sceGuTexFilter(psp->tex_filter, psp->tex_filter); if (((uint32_t)frame&0x04000000) || (frame == RETRO_HW_FRAME_BUFFER_VALID)) // frame in VRAM ? texture/palette was set in core so draw directly { sceGuClear(GU_COLOR_BUFFER_BIT); sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_COLOR_4444 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 2, NULL, (void*)(psp->frame_coords)); } else { if (frame!=NULL) { sceKernelDcacheWritebackRange(frame,pitch * height); sceGuCopyImage(GU_PSM_5650, ((u32)frame & 0xF) >> psp->bpp_log2, 0, width, height, pitch >> psp->bpp_log2, (void*)((u32)frame & ~0xF), 0, 0, width, psp->texture); } sceGuClear(GU_COLOR_BUFFER_BIT); sceGuTexImage(0, next_pow2(width), next_pow2(height), width, psp->texture); sceGuCallList(psp->frame_dList); } sceGuFinish(); RARCH_PERFORMANCE_STOP(psp_frame_run); if(psp->menu.active) { sceGuSendList(GU_TAIL, psp->menu.dList, &(psp->menu.context_storage)); sceGuSync(0, 0); } return true; } static void psp_set_nonblock_state(void *data, bool toggle) { psp1_video_t *psp = (psp1_video_t*)data; psp->vsync = !toggle; } static bool psp_alive(void *data) { (void)data; return true; } static bool psp_focus(void *data) { (void)data; return true; } static void psp_free(void *data) { psp1_video_t *psp = (psp1_video_t*)data; if(!(psp) || !(psp->main_dList)) return; sceDisplayWaitVblankStart(); sceGuDisplay(GU_FALSE); sceGuTerm(); if (psp->main_dList) free(psp->main_dList); if (psp->frame_dList) free(psp->frame_dList); if (psp->frame_coords) free(TO_CACHED_PTR(psp->frame_coords)); if (psp->menu.frame_coords) free(TO_CACHED_PTR(psp->menu.frame_coords)); if (psp->menu.dList) free(psp->menu.dList); if (psp->menu.frame) free(psp->menu.frame); free(data); sceKernelDisableSubIntr(PSP_VBLANK_INT, 0); sceKernelReleaseSubIntrHandler(PSP_VBLANK_INT,0); } static void psp_set_texture_frame(void *data, const void *frame, bool rgb32, unsigned width, unsigned height, float alpha) { (void) rgb32; (void) alpha; int i; psp1_video_t *psp = (psp1_video_t*)data; #ifdef DEBUG rarch_assert((width*height) < (480 * 272)); // psp->menu.frame buffer size is (480 * 272)*2 Bytes #endif // rendering the menu frame as a single sprite is slow // so we render it as 16 vertical stripes instead for (i=0;i<16;i++) { psp->menu.frame_coords[i].v0.x = (i) * SCEGU_SCR_WIDTH / 16 ; psp->menu.frame_coords[i].v1.x = (i+1) * SCEGU_SCR_WIDTH / 16 ; //psp->menu.frame_coords[i].v0.y = 0; psp->menu.frame_coords[i].v1.y = SCEGU_SCR_HEIGHT ; psp->menu.frame_coords[i].v0.u = (i) * width / 16 ; psp->menu.frame_coords[i].v1.u = (i+1) * width / 16 ; //psp->menu.frame_coords[i].v0.v = 0; psp->menu.frame_coords[i].v1.v = height; } sceKernelDcacheWritebackRange(frame,width * height * 2); sceGuStart(GU_DIRECT, psp->main_dList); sceGuCopyImage(GU_PSM_4444, 0, 0, width, height, width, (void*)frame, 0, 0, width, psp->menu.frame); sceGuFinish(); sceGuStart(GU_SEND, psp->menu.dList); sceGuTexSync(); sceGuTexMode(GU_PSM_4444, 0, 0, GU_FALSE); sceGuTexFunc(GU_TFX_REPLACE, GU_TCC_RGB); sceGuTexFilter(GU_LINEAR, GU_LINEAR); sceGuTexImage(0, next_pow2(width), next_pow2(height), width, psp->menu.frame); sceGuEnable(GU_BLEND); // sceGuBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0); // default blending sceGuBlendFunc(GU_ADD, GU_FIX, GU_FIX, 0xF0F0F0F0, 0x0F0F0F0F); ; sceGuDrawArray(GU_SPRITES, GU_TEXTURE_16BIT | GU_COLOR_4444 | GU_VERTEX_16BIT | GU_TRANSFORM_2D, 32, NULL, psp->menu.frame_coords); sceGuFinish(); } static void psp_set_texture_enable(void *data, bool state, bool full_screen) { (void) full_screen; psp1_video_t *psp = (psp1_video_t*)data; psp->menu.active = state; } static void psp_update_frame_coords(psp1_video_t* psp) { psp1_vertex_t *v0, *v1; if (psp->rotation & 0x2) { v0 = &psp->frame_coords->v1; v1 = &psp->frame_coords->v0; } else { v0 = &psp->frame_coords->v0; v1 = &psp->frame_coords->v1; } if (psp->rotation & 0x1) { v0->x = psp->vp.x + psp->vp.width; v1->x = psp->vp.x; } else { v0->x = psp->vp.x; v1->x = psp->vp.x + psp->vp.width; } v0->y = psp->vp.y; v1->y = psp->vp.y + psp->vp.height; } static void psp_update_viewport(psp1_video_t* psp) { int x, y; float device_aspect = SCEGU_SCR_WIDTH / SCEGU_SCR_HEIGHT; float width = SCEGU_SCR_WIDTH; float height = SCEGU_SCR_HEIGHT; x = 0; y = 0; if (g_settings.video.scale_integer) { gfx_scale_integer(&psp->vp, SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT, g_extern.system.aspect_ratio, psp->keep_aspect); width = psp->vp.width; height = psp->vp.height; } else if (psp->keep_aspect) { float desired_aspect = g_extern.system.aspect_ratio; float delta; #if defined(HAVE_MENU) if (g_settings.video.aspect_ratio_idx == ASPECT_RATIO_CUSTOM) { const struct rarch_viewport *custom = &g_extern.console.screen.viewports.custom_vp; x = custom->x; y = custom->y; width = custom->width; height = custom->height; } else #endif { if ((fabsf(device_aspect - desired_aspect) < 0.0001f)||(fabsf((16.0/9.0) - desired_aspect) < 0.0001f)) { // If the aspect ratios of screen and desired aspect ratio are sufficiently equal (floating point stuff), // assume they are actually equal. } else if (device_aspect > desired_aspect) { delta = (desired_aspect / device_aspect - 1.0f) / 2.0f + 0.5f; x = (int)roundf(width * (0.5f - delta)); width = (unsigned)roundf(2.0f * width * delta); } else { delta = (device_aspect / desired_aspect - 1.0f) / 2.0f + 0.5f; y = (int)roundf(height * (0.5f - delta)); height = (unsigned)roundf(2.0f * height * delta); } } psp->vp.x = x; psp->vp.y = y; psp->vp.width = width; psp->vp.height = height; } else { psp->vp.x = psp->vp.y = 0; psp->vp.width = width; psp->vp.height = height; } psp_update_frame_coords(psp); psp->should_resize = false; } static void psp_set_rotation(void *data, unsigned rotation) { psp1_video_t *psp = (psp1_video_t*)data; psp->rotation = rotation; psp->should_resize = true; } static void psp_set_filtering(void *data, unsigned index, bool smooth) { psp1_video_t *psp = (psp1_video_t*)data; psp->tex_filter = smooth? GU_LINEAR : GU_NEAREST; } static void psp_set_aspect_ratio(void *data, unsigned aspectratio_index) { psp1_video_t *psp = (psp1_video_t*)data; switch (aspectratio_index) { case ASPECT_RATIO_SQUARE: gfx_set_square_pixel_viewport(g_extern.system.av_info.geometry.base_width, g_extern.system.av_info.geometry.base_height); break; case ASPECT_RATIO_CORE: gfx_set_core_viewport(); break; case ASPECT_RATIO_CONFIG: gfx_set_config_viewport(); break; default: break; } g_extern.system.aspect_ratio = aspectratio_lut[aspectratio_index].value; psp->keep_aspect = true; psp->should_resize = true; } static void psp_apply_state_changes(void *data) { psp1_video_t *psp = (psp1_video_t*)data; psp->should_resize = true; } static void psp_viewport_info(void *data, struct rarch_viewport *vp) { psp1_video_t *psp = (psp1_video_t*)data; *vp = psp->vp; } static bool psp_read_viewport(void *data, uint8_t *buffer) { return false; } static const video_poke_interface_t psp_poke_interface = { psp_set_filtering, psp_set_aspect_ratio, psp_apply_state_changes, #ifdef HAVE_MENU psp_set_texture_frame, psp_set_texture_enable, #endif NULL, NULL, NULL }; static void psp_get_poke_interface(void *data, const video_poke_interface_t **iface) { (void)data; *iface = &psp_poke_interface; } const video_driver_t video_psp1 = { psp_init, psp_frame, psp_set_nonblock_state, psp_alive, psp_focus, NULL, psp_free, "psp1", psp_set_rotation, psp_viewport_info, NULL, /* psp_read_viewport */ #ifdef HAVE_OVERLAY NULL, #endif psp_get_poke_interface };