2161 lines
74 KiB
C
2161 lines
74 KiB
C
/* src/psp/psp-video.c: PSP video interface module
|
|
Copyright 2009-2010 Andrew Church
|
|
Based on src/vidogl.c by Guillaume Duhamel and others
|
|
|
|
This file is part of Yabause.
|
|
|
|
Yabause 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 Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Yabause 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 Yabause; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "common.h"
|
|
|
|
#include "../vdp1.h"
|
|
#include "../vdp2.h"
|
|
#include "../vidshared.h"
|
|
|
|
#include "config.h"
|
|
#include "display.h"
|
|
#include "font.h"
|
|
#include "gu.h"
|
|
#include "misc.h"
|
|
#include "psp-video.h"
|
|
#include "psp-video-internal.h"
|
|
#include "texcache.h"
|
|
#include "timing.h"
|
|
|
|
/*************************************************************************/
|
|
/************************* Interface definition **************************/
|
|
/*************************************************************************/
|
|
|
|
/* Interface function declarations (must come before interface definition) */
|
|
|
|
static int psp_video_init(void);
|
|
static void psp_video_deinit(void);
|
|
static void psp_video_resize(unsigned int width, unsigned int height,
|
|
int fullscreen);
|
|
static int psp_video_is_fullscreen(void);
|
|
static void psp_video_debug_message(char *format, ...);
|
|
|
|
static int psp_vdp1_reset(void);
|
|
static void psp_vdp1_draw_start(void);
|
|
static void psp_vdp1_draw_end(void);
|
|
static void psp_vdp1_normal_sprite_draw(void);
|
|
static void psp_vdp1_scaled_sprite_draw(void);
|
|
static void psp_vdp1_distorted_sprite_draw(void);
|
|
static void psp_vdp1_polygon_draw(void);
|
|
static void psp_vdp1_polyline_draw(void);
|
|
static void psp_vdp1_line_draw(void);
|
|
static void psp_vdp1_user_clipping(void);
|
|
static void psp_vdp1_system_clipping(void);
|
|
static void psp_vdp1_local_coordinate(void);
|
|
|
|
static int psp_vdp2_reset(void);
|
|
static void psp_vdp2_draw_start(void);
|
|
static void psp_vdp2_draw_end(void);
|
|
static void psp_vdp2_draw_screens(void);
|
|
static void psp_vdp2_set_resolution(u16 TVMD);
|
|
static void FASTCALL psp_vdp2_set_priority_NBG0(int priority);
|
|
static void FASTCALL psp_vdp2_set_priority_NBG1(int priority);
|
|
static void FASTCALL psp_vdp2_set_priority_NBG2(int priority);
|
|
static void FASTCALL psp_vdp2_set_priority_NBG3(int priority);
|
|
static void FASTCALL psp_vdp2_set_priority_RBG0(int priority);
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/* Module interface definition */
|
|
|
|
VideoInterface_struct VIDPSP = {
|
|
.id = VIDCORE_PSP,
|
|
.Name = "PSP Video Interface",
|
|
.Init = psp_video_init,
|
|
.DeInit = psp_video_deinit,
|
|
.Resize = psp_video_resize,
|
|
.IsFullscreen = psp_video_is_fullscreen,
|
|
.OnScreenDebugMessage = psp_video_debug_message,
|
|
|
|
.Vdp1Reset = psp_vdp1_reset,
|
|
.Vdp1DrawStart = psp_vdp1_draw_start,
|
|
.Vdp1DrawEnd = psp_vdp1_draw_end,
|
|
.Vdp1NormalSpriteDraw = psp_vdp1_normal_sprite_draw,
|
|
.Vdp1ScaledSpriteDraw = psp_vdp1_scaled_sprite_draw,
|
|
.Vdp1DistortedSpriteDraw = psp_vdp1_distorted_sprite_draw,
|
|
.Vdp1PolygonDraw = psp_vdp1_polygon_draw,
|
|
.Vdp1PolylineDraw = psp_vdp1_polyline_draw,
|
|
.Vdp1LineDraw = psp_vdp1_line_draw,
|
|
.Vdp1UserClipping = psp_vdp1_user_clipping,
|
|
.Vdp1SystemClipping = psp_vdp1_system_clipping,
|
|
.Vdp1LocalCoordinate = psp_vdp1_local_coordinate,
|
|
|
|
.Vdp2Reset = psp_vdp2_reset,
|
|
.Vdp2DrawStart = psp_vdp2_draw_start,
|
|
.Vdp2DrawEnd = psp_vdp2_draw_end,
|
|
.Vdp2DrawScreens = psp_vdp2_draw_screens,
|
|
.Vdp2SetResolution = psp_vdp2_set_resolution,
|
|
.Vdp2SetPriorityNBG0 = psp_vdp2_set_priority_NBG0,
|
|
.Vdp2SetPriorityNBG1 = psp_vdp2_set_priority_NBG1,
|
|
.Vdp2SetPriorityNBG2 = psp_vdp2_set_priority_NBG2,
|
|
.Vdp2SetPriorityNBG3 = psp_vdp2_set_priority_NBG3,
|
|
.Vdp2SetPriorityRBG0 = psp_vdp2_set_priority_RBG0,
|
|
};
|
|
|
|
/*************************************************************************/
|
|
/************************* Global and local data *************************/
|
|
/*************************************************************************/
|
|
|
|
/**** Exported data ****/
|
|
|
|
/* Color table generated from VDP2 color RAM */
|
|
__attribute__((aligned(64))) uint16_t global_clut_16[0x800];
|
|
__attribute__((aligned(64))) uint32_t global_clut_32[0x800];
|
|
|
|
/* Displayed width and height */
|
|
unsigned int disp_width, disp_height;
|
|
|
|
/* Scale (right-shift) applied to X and Y coordinates */
|
|
unsigned int disp_xscale, disp_yscale;
|
|
|
|
/* Total number of frames to skip before we draw the next one */
|
|
unsigned int frames_to_skip;
|
|
|
|
/* Number of frames skipped so far since we drew the last one */
|
|
unsigned int frames_skipped;
|
|
|
|
/* VDP1 color component offset values (-0xFF...+0xFF) */
|
|
int32_t vdp1_rofs, vdp1_gofs, vdp1_bofs;
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**** Internal data ****/
|
|
|
|
/*----------------------------------*/
|
|
|
|
/* Pending infoline text (malloc()ed, or NULL if none) and color */
|
|
static char *infoline_text;
|
|
static uint32_t infoline_color;
|
|
|
|
/*----------------------------------*/
|
|
|
|
/* Current average frame rate (rolling average) */
|
|
static float average_fps;
|
|
|
|
/* Flag indicating whether graphics should be drawn this frame */
|
|
static uint8_t draw_graphics;
|
|
|
|
/* Background priorities (NBG0, NBG1, NBG2, NBG3, RBG0) */
|
|
static uint8_t bg_priority[5];
|
|
|
|
/*----------------------------------*/
|
|
|
|
/* Custom drawing function specified for each background layer */
|
|
static CustomDrawRoutine *custom_draw_func[5];
|
|
|
|
/* Is the RBG0 drawing function fast enough to consider it a normal layer
|
|
* for timing purposes? */
|
|
static uint8_t RBG0_draw_func_is_fast;
|
|
|
|
/* Did we draw a slow RBG0 this frame? */
|
|
static uint8_t drew_slow_RBG0;
|
|
|
|
/*----------------------------------*/
|
|
|
|
/* Rendering data for sprites, polygons, and lines (a copy of all
|
|
* parameters except priority passed to vdp1_render_queue() */
|
|
typedef struct VDP1RenderData_ {
|
|
uint32_t texture_key;
|
|
int primitive;
|
|
int vertex_type;
|
|
int count;
|
|
const void *indices;
|
|
const void *vertices;
|
|
} VDP1RenderData;
|
|
|
|
/* VDP1 render queues (one for each priority level) */
|
|
typedef struct VDP1RenderQueue_ {
|
|
VDP1RenderData *queue; // Array of entries (dynamically expanded)
|
|
int size; // Size of queue array, in entries
|
|
int len; // Number of entries currently in array
|
|
} VDP1RenderQueue;
|
|
static VDP1RenderQueue vdp1_queue[8];
|
|
|
|
/* Amount to expand a queue's array when it gets full */
|
|
#define VDP1_QUEUE_EXPAND_SIZE 1000
|
|
|
|
/*----------------------------------*/
|
|
|
|
/* Flags indicating whether each 4k page of VDP1/2 RAM contains any
|
|
* persistently-cached texture data */
|
|
static uint8_t vdp1_page_cached[0x80], vdp2_page_cached[0x80];
|
|
|
|
/* Checksum of each VDP1/2 RAM page containing cached texture data */
|
|
static uint32_t vdp1_page_checksum[0x80], vdp2_page_checksum[0x80];
|
|
|
|
/* State of color offset settings at last cache reset */
|
|
static uint32_t vdp1_cached_cofs;
|
|
static uint32_t vdp2_cached_cofs_regs; // CLOFEN<<16 | CLOFSL
|
|
static uint32_t vdp2_cached_cofs_A, vdp2_cached_cofs_B;
|
|
|
|
/*************************************************************************/
|
|
|
|
/**** Local function declarations ****/
|
|
|
|
static int vdp1_is_persistent(vdp1cmd_struct *cmd);
|
|
static void vdp1_draw_lines(vdp1cmd_struct *cmd, int poly);
|
|
static void vdp1_draw_quad(vdp1cmd_struct *cmd, int textured);
|
|
static uint32_t vdp1_convert_color(uint16_t color16, int textured,
|
|
unsigned int CMDPMOD);
|
|
static uint32_t vdp1_get_cmd_color(vdp1cmd_struct *cmd);
|
|
static uint32_t vdp1_get_cmd_color_pri(vdp1cmd_struct *cmd, int textured,
|
|
int *priority_ret);
|
|
static uint16_t vdp1_process_sprite_color(uint16_t color16, int *priority_ret,
|
|
int *alpha_ret);
|
|
static uint32_t vdp1_cache_sprite_texture(
|
|
vdp1cmd_struct *cmd, int width, int height, int *priority_ret,
|
|
int *alpha_ret);
|
|
static inline void vdp1_queue_render(
|
|
int priority, uint32_t texture_key, int primitive,
|
|
int vertex_type, int count, const void *indices, const void *vertices);
|
|
static void vdp1_run_queue(int priority);
|
|
|
|
static inline void vdp2_get_color_offsets(uint16_t mask, int32_t *rofs_ret,
|
|
int32_t *gofs_ret, int32_t *bofs_ret);
|
|
|
|
static void vdp2_draw_bg(void);
|
|
static void vdp2_draw_graphics(int layer);
|
|
|
|
/*************************************************************************/
|
|
/********************** General interface functions **********************/
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_video_init: Initialize the peripheral interface.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* Zero on success, negative on error
|
|
*/
|
|
static int psp_video_init(void)
|
|
{
|
|
/* Set some reasonable defaults. */
|
|
disp_width = 320;
|
|
disp_height = 224;
|
|
disp_xscale = 0;
|
|
disp_yscale = 0;
|
|
|
|
/* Always draw the first frame. */
|
|
frames_to_skip = 0;
|
|
frames_skipped = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* psp_video_deinit: Shut down the peripheral interface.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_video_deinit(void)
|
|
{
|
|
/* We don't implement shutting down, so nothing to do. */
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_video_resize: Resize the display window. A no-op on PSP.
|
|
*
|
|
* [Parameters]
|
|
* width: New window width
|
|
* height: New window height
|
|
* fullscreen: Nonzero to use fullscreen mode, else zero
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_video_resize(unsigned int width, unsigned int height,
|
|
int fullscreen)
|
|
{
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_video_is_fullscreen: Return whether the display is currently in
|
|
* fullscreen mode. Always returns true (nonzero) on PSP.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* Nonzero if in fullscreen mode, else zero
|
|
*/
|
|
static int psp_video_is_fullscreen(void)
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_video_debug_message: Display a debug message on the screen.
|
|
*
|
|
* [Parameters]
|
|
* format: printf()-style format string
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_video_debug_message(char *format, ...)
|
|
{
|
|
/* Not implemented */
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/********************* PSP-only interface functions **********************/
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_video_infoline: Display an information line on the bottom of the
|
|
* screen. The text will be displayed for one frame only; call this
|
|
* function every frame to keep the text visible.
|
|
*
|
|
* [Parameters]
|
|
* color: Text color (0xAABBGGRR)
|
|
* text: Text string
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
void psp_video_infoline(uint32_t color, const char *text)
|
|
{
|
|
infoline_text = strdup(text);
|
|
if (UNLIKELY(!infoline_text)) {
|
|
DMSG("Failed to strdup(%s)", text);
|
|
}
|
|
infoline_color = color;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_video_set_draw_routine: Set a custom drawing routine for a specific
|
|
* graphics layer. If "is_fast" is true when setting a routine for RBG0,
|
|
* the frame rate will not be halved regardless of the related setting in
|
|
* the configuration menu.
|
|
*
|
|
* [Parameters]
|
|
* layer: Graphics layer (BG_*)
|
|
* func: Drawing routine (NULL to clear any previous setting)
|
|
* is_fast: For BG_RBG0, indicates whether the routine is fast enough
|
|
* to be considered a non-distorted layer for the purposes
|
|
* of frame rate adjustment; ignored for other layers
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
void psp_video_set_draw_routine(int layer, CustomDrawRoutine *func,
|
|
int is_fast)
|
|
{
|
|
PRECOND(layer >= BG_NBG0 && layer <= BG_RBG0, return);
|
|
custom_draw_func[layer] = func;
|
|
if (layer == BG_RBG0) {
|
|
RBG0_draw_func_is_fast = is_fast;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp2_is_persistent: Return whether the tile at the given address in
|
|
* VDP2 RAM is persistently cacheable.
|
|
*
|
|
* [Parameters]
|
|
* address: Tile address in VDP2 RAM
|
|
* [Return value]
|
|
* Nonzero if tile texture can be persistently cached, else zero
|
|
*/
|
|
int vdp2_is_persistent(uint32_t address)
|
|
{
|
|
const unsigned int page = address >> 12;
|
|
if (!vdp2_page_cached[page]) {
|
|
vdp2_page_checksum[page] =
|
|
checksum_fast32((const uint32_t *)(Vdp2Ram + (page<<12)), 1024);
|
|
vdp2_page_cached[page] = 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/******************* VDP1-specific interface functions *******************/
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_reset: Reset the VDP1 state.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* Unknown (always zero)
|
|
*/
|
|
static int psp_vdp1_reset(void)
|
|
{
|
|
/* Nothing to do. */
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_draw_start: Prepare for VDP1 drawing.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_draw_start(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
/* Clear out all the rendering queues (just to be safe). */
|
|
int priority;
|
|
for (priority = 0; priority < 8; priority++) {
|
|
vdp1_queue[priority].len = 0;
|
|
}
|
|
|
|
/* Get the color offsets. */
|
|
vdp2_get_color_offsets(1<<6, &vdp1_rofs, &vdp1_gofs, &vdp1_bofs);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
|
|
/**
|
|
* psp_vdp1_draw_end: Finish VDP1 drawing.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_draw_end(void)
|
|
{
|
|
/* Nothing to do */
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
|
|
/**
|
|
* psp_vdp1_normal_sprite_draw: Draw an unscaled rectangular sprite.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_normal_sprite_draw(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
vdp1cmd_struct cmd;
|
|
Vdp1ReadCommand(&cmd, Vdp1Regs->addr);
|
|
|
|
int width = ((cmd.CMDSIZE >> 8) & 0x3F) * 8;
|
|
int height = cmd.CMDSIZE & 0xFF;
|
|
cmd.CMDXB = cmd.CMDXA + width; cmd.CMDYB = cmd.CMDYA;
|
|
cmd.CMDXC = cmd.CMDXA + width; cmd.CMDYC = cmd.CMDYA + height;
|
|
cmd.CMDXD = cmd.CMDXA; cmd.CMDYD = cmd.CMDYA + height;
|
|
|
|
vdp1_draw_quad(&cmd, 1);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_scaled_sprite_draw: Draw a scaled rectangular sprite.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_scaled_sprite_draw(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
vdp1cmd_struct cmd;
|
|
Vdp1ReadCommand(&cmd, Vdp1Regs->addr);
|
|
|
|
if (!(cmd.CMDCTRL & 0x0F00)) {
|
|
/* Size is directly specified. */
|
|
cmd.CMDXC++; cmd.CMDYC++;
|
|
cmd.CMDXB = cmd.CMDXC; cmd.CMDYB = cmd.CMDYA;
|
|
cmd.CMDXD = cmd.CMDXA; cmd.CMDYD = cmd.CMDYC;
|
|
} else {
|
|
/* Scale around a particular point (left/top, center, right/bottom). */
|
|
int new_w = cmd.CMDXB + 1;
|
|
int new_h = cmd.CMDYB + 1;
|
|
if ((cmd.CMDCTRL & 0x300) == 0x200) {
|
|
cmd.CMDXA -= cmd.CMDXB / 2;
|
|
} else if ((cmd.CMDCTRL & 0x300) == 0x300) {
|
|
cmd.CMDXA -= cmd.CMDXB;
|
|
}
|
|
if ((cmd.CMDCTRL & 0xC00) == 0x800) {
|
|
cmd.CMDYA -= cmd.CMDYB / 2;
|
|
} else if ((cmd.CMDCTRL & 0xC00) == 0xC00) {
|
|
cmd.CMDYA -= cmd.CMDYB;
|
|
}
|
|
cmd.CMDXB = cmd.CMDXA + new_w; cmd.CMDYB = cmd.CMDYA;
|
|
cmd.CMDXC = cmd.CMDXA + new_w; cmd.CMDYC = cmd.CMDYA + new_h;
|
|
cmd.CMDXD = cmd.CMDXA; cmd.CMDYD = cmd.CMDYA + new_h;
|
|
}
|
|
|
|
vdp1_draw_quad(&cmd, 1);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_distorted_sprite_draw: Draw a sprite on an arbitrary
|
|
* quadrilateral.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_distorted_sprite_draw(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
vdp1cmd_struct cmd;
|
|
Vdp1ReadCommand(&cmd, Vdp1Regs->addr);
|
|
vdp1_draw_quad(&cmd, 1);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_polygon_draw: Draw an untextured quadrilateral.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_polygon_draw(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
vdp1cmd_struct cmd;
|
|
Vdp1ReadCommand(&cmd, Vdp1Regs->addr);
|
|
vdp1_draw_quad(&cmd, 0);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_polyline_draw: Draw four connected lines.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_polyline_draw(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
vdp1cmd_struct cmd;
|
|
Vdp1ReadCommand(&cmd, Vdp1Regs->addr);
|
|
vdp1_draw_lines(&cmd, 1);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_line_draw: Draw a single line.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_line_draw(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
vdp1cmd_struct cmd;
|
|
Vdp1ReadCommand(&cmd, Vdp1Regs->addr);
|
|
vdp1_draw_lines(&cmd, 0);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_user_clipping: Set the user clipping coordinates.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_user_clipping(void)
|
|
{
|
|
Vdp1Regs->userclipX1 = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0xC);
|
|
Vdp1Regs->userclipY1 = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0xE);
|
|
Vdp1Regs->userclipX2 = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0x14);
|
|
Vdp1Regs->userclipY2 = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0x16);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_system_clipping: Set the system clipping coordinates.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_system_clipping(void)
|
|
{
|
|
Vdp1Regs->systemclipX1 = 0;
|
|
Vdp1Regs->systemclipY1 = 0;
|
|
Vdp1Regs->systemclipX2 = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0x14);
|
|
Vdp1Regs->systemclipY2 = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0x16);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp1_local_coordinate: Set coordinate offset values used in drawing
|
|
* primitives.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp1_local_coordinate(void)
|
|
{
|
|
Vdp1Regs->localX = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0xC);
|
|
Vdp1Regs->localY = T1ReadWord(Vdp1Ram, Vdp1Regs->addr + 0xE);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/******************* VDP2-specific interface functions *******************/
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp2_reset: Reset the VDP2 state.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* Unknown (always zero)
|
|
*/
|
|
static int psp_vdp2_reset(void)
|
|
{
|
|
/* Nothing to do */
|
|
return 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp2_draw_start: Begin drawing a video frame.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp2_draw_start(void)
|
|
{
|
|
/* Apply any game-specific optimizations or tweaks. (This may involve
|
|
* adjusting the frame-skip variables, so we call it before the
|
|
* frame-skip check.) */
|
|
psp_video_apply_tweaks();
|
|
|
|
/* If we're skipping this frame, we don't do anything, not even start
|
|
* a new output frame (because that forces a VBlank sync, which may
|
|
* waste time if the previous frame completed quickly). */
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
/* Load the global color lookup tables from VDP2 color RAM. */
|
|
const uint16_t *cram = (const uint16_t *)Vdp2ColorRam;
|
|
if (Vdp2Internal.ColorMode == 2) { // 32-bit color table
|
|
int i;
|
|
for (i = 0; i < 0x400; i++) {
|
|
uint16_t xb = cram[i*2+0];
|
|
uint16_t gr = cram[i*2+1];
|
|
uint16_t color16 = 0x8000 | (xb<<7 & 0x7C00)
|
|
| (gr<<2 & 0x3E0) | (gr>>3 & 0x1F);
|
|
uint32_t color32 = 0xFF000000 | xb<<16 | gr;
|
|
global_clut_16[i] = color16;
|
|
global_clut_16[i+0x400] = color16;
|
|
global_clut_32[i] = color32;
|
|
global_clut_32[i+0x400] = color32;
|
|
}
|
|
} else { // 16-bit color table
|
|
int i;
|
|
for (i = 0; i < 0x800; i++) {
|
|
uint16_t color16 = 0x8000 | cram[i];
|
|
uint32_t color32 = 0xFF000000 | (color16 & 0x7C00) << 9
|
|
| (color16 & 0x03E0) << 6
|
|
| (color16 & 0x001F) << 3;
|
|
global_clut_16[i] = color16;
|
|
global_clut_32[i] = color32;
|
|
}
|
|
}
|
|
|
|
/* Start a new frame. */
|
|
display_set_size(disp_width >> disp_xscale, disp_height >> disp_yscale);
|
|
display_begin_frame();
|
|
|
|
/* Clear the texture cache of transient data; also clear persistent
|
|
* data if any source RAM or color offsets were changed, or if
|
|
* persistent caching is disabled in the first place. */
|
|
const uint32_t vdp1_cofs = (vdp1_rofs & 0x1FF) << 18
|
|
| (vdp1_gofs & 0x1FF) << 9
|
|
| (vdp1_bofs & 0x1FF) << 0;
|
|
const uint32_t vdp2_cofs_regs = Vdp2Regs->CLOFEN << 16 | Vdp2Regs->CLOFSL;
|
|
const uint32_t vdp2_cofs_A = (Vdp2Regs->COAR & 0x1FF) << 18
|
|
| (Vdp2Regs->COAG & 0x1FF) << 9
|
|
| (Vdp2Regs->COAB & 0x1FF) << 0;
|
|
const uint32_t vdp2_cofs_B = (Vdp2Regs->COBR & 0x1FF) << 18
|
|
| (Vdp2Regs->COBG & 0x1FF) << 9
|
|
| (Vdp2Regs->COBB & 0x1FF) << 0;
|
|
int need_reset = 0;
|
|
if (!config_get_cache_textures()) {
|
|
need_reset = 1;
|
|
} else if (vdp1_cofs != vdp1_cached_cofs
|
|
|| vdp2_cofs_regs != vdp2_cached_cofs_regs
|
|
|| vdp2_cofs_A != vdp2_cached_cofs_A
|
|
|| vdp2_cofs_B != vdp2_cached_cofs_B) {
|
|
DMSG("Color offsets changed, clearing cache");
|
|
need_reset = 1;
|
|
} else {
|
|
unsigned int page;
|
|
for (page = 0; page < 0x80; page++) {
|
|
if (vdp1_page_cached[page]) {
|
|
const uint32_t sum =
|
|
checksum_fast32((const uint32_t *)(Vdp1Ram + (page<<12)), 1024);
|
|
if (sum != vdp1_page_checksum[page]) {
|
|
DMSG("VDP1 page 0x%05X checksum changed (%08X -> %08X),"
|
|
" clearing cache",
|
|
page<<12, vdp1_page_checksum[page], sum);
|
|
need_reset = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (vdp2_page_cached[page]) {
|
|
const uint32_t sum =
|
|
checksum_fast32((const uint32_t *)(Vdp2Ram + (page<<12)), 1024);
|
|
if (sum != vdp2_page_checksum[page]) {
|
|
DMSG("VDP2 page 0x%05X checksum changed (%08X -> %08X),"
|
|
" clearing cache",
|
|
page<<12, vdp2_page_checksum[page], sum);
|
|
need_reset = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (need_reset) {
|
|
texcache_reset();
|
|
memset(vdp1_page_cached, 0, sizeof(vdp1_page_cached));
|
|
memset(vdp2_page_cached, 0, sizeof(vdp2_page_cached));
|
|
} else {
|
|
texcache_clean();
|
|
}
|
|
vdp1_cached_cofs = vdp1_cofs;
|
|
vdp2_cached_cofs_regs = vdp2_cofs_regs;
|
|
vdp2_cached_cofs_A = vdp2_cofs_A;
|
|
vdp2_cached_cofs_B = vdp2_cofs_B;
|
|
|
|
/* Initialize the render state. */
|
|
guTexFilter(GU_NEAREST, GU_NEAREST);
|
|
guTexWrap(GU_CLAMP, GU_CLAMP);
|
|
guTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA);
|
|
guBlendFunc(GU_ADD, GU_SRC_ALPHA, GU_ONE_MINUS_SRC_ALPHA, 0, 0);
|
|
guEnable(GU_BLEND); // We treat everything as alpha-enabled
|
|
|
|
/* Reset the draw-graphics flag (it will be set by draw_screens() if
|
|
* graphics are active). */
|
|
draw_graphics = 0;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp2_draw_end: Finish drawing a video frame.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp2_draw_end(void)
|
|
{
|
|
if (frames_skipped >= frames_to_skip) {
|
|
|
|
/* Draw all graphics by priority. */
|
|
int priority;
|
|
for (priority = 0; priority < 8; priority++) {
|
|
/* Draw background graphics first... */
|
|
if (draw_graphics && priority > 0) {
|
|
if (bg_priority[BG_NBG3] == priority) {
|
|
vdp2_draw_graphics(BG_NBG3);
|
|
}
|
|
if (bg_priority[BG_NBG2] == priority) {
|
|
vdp2_draw_graphics(BG_NBG2);
|
|
}
|
|
if (bg_priority[BG_NBG1] == priority) {
|
|
vdp2_draw_graphics(BG_NBG1);
|
|
}
|
|
if (bg_priority[BG_NBG0] == priority) {
|
|
vdp2_draw_graphics(BG_NBG0);
|
|
}
|
|
if (bg_priority[BG_RBG0] == priority) {
|
|
vdp2_draw_graphics(BG_RBG0);
|
|
}
|
|
}
|
|
/* Then draw sprites on top... */
|
|
vdp1_run_queue(priority);
|
|
/* And clear the rendering queue. */
|
|
vdp1_queue[priority].len = 0;
|
|
}
|
|
|
|
/* Always compute average FPS (even if we're not showing it), so
|
|
* the value is accurate as soon as the display is turned on.
|
|
* We use a rolling average that decays by 50% every second. */
|
|
unsigned int frame_length = display_last_frame_length();
|
|
if (frame_length == 0) {
|
|
frame_length = 1; // Just in case (avoid division by 0)
|
|
}
|
|
unsigned int frame_count = 1 + frames_skipped;
|
|
const float fps = (frame_count*60.0f) / frame_length;
|
|
if (!average_fps) {
|
|
/* When first starting up, just set the average to the first
|
|
* frame's frame rate. */
|
|
average_fps = fps;
|
|
} else {
|
|
const float weight = powf(2.0f, -(1/fps));
|
|
average_fps = (average_fps * weight) + (fps * (1-weight));
|
|
}
|
|
if (config_get_show_fps()) {
|
|
unsigned int show_fps = iroundf(average_fps*10);
|
|
if (show_fps > 600) {
|
|
/* FPS may momentarily exceed 60.0 due to timing jitter,
|
|
* but we never show more than 60.0. */
|
|
show_fps = 600;
|
|
}
|
|
font_printf((disp_width >> disp_xscale) - 2, 2, 1, 0xAAFF8040,
|
|
"FPS: %2d.%d (%d/%2d)", show_fps/10, show_fps%10,
|
|
frame_count, frame_length);
|
|
}
|
|
|
|
if (infoline_text) {
|
|
font_printf((disp_width >> disp_xscale) / 2,
|
|
(disp_height >> disp_yscale) - FONT_HEIGHT - 2, 0,
|
|
infoline_color, "%s", infoline_text);
|
|
free(infoline_text);
|
|
infoline_text = NULL;
|
|
}
|
|
|
|
display_end_frame();
|
|
|
|
} // if (frames_skipped >= frames_to_skip)
|
|
|
|
if (frames_skipped < frames_to_skip) {
|
|
frames_skipped++;
|
|
timing_skip_next_sync(); // Let the emulation continue uninterrupted
|
|
} else {
|
|
frames_skipped = 0;
|
|
if (config_get_frameskip_auto()) {
|
|
// FIXME: auto frame skipping not yet implemented
|
|
frames_to_skip = 0;
|
|
} else {
|
|
frames_to_skip = config_get_frameskip_num();
|
|
}
|
|
if (drew_slow_RBG0) {
|
|
frames_to_skip += 1 + frames_to_skip;
|
|
}
|
|
if (disp_height > 272 && frames_to_skip == 0
|
|
&& config_get_frameskip_interlace()
|
|
) {
|
|
frames_to_skip = 1;
|
|
}
|
|
drew_slow_RBG0 = 0;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp2_draw_screens: Draw the VDP2 background and graphics layers.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp2_draw_screens(void)
|
|
{
|
|
if (frames_skipped < frames_to_skip) {
|
|
return;
|
|
}
|
|
|
|
/* Draw the background color(s). */
|
|
vdp2_draw_bg();
|
|
|
|
/* Flag the background graphics to be drawn. */
|
|
draw_graphics = 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp2_set_resolution: Change the resolution of the Saturn display.
|
|
*
|
|
* [Parameters]
|
|
* TVMD: New value of the VDP2 TVMD register
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void psp_vdp2_set_resolution(u16 TVMD)
|
|
{
|
|
/* Set the display width from bits 0-1. */
|
|
disp_width = (TVMD & 1) ? 352 : 320;
|
|
if (TVMD & 2) {
|
|
disp_width *= 2;
|
|
}
|
|
|
|
/* Set the display height from bits 4-5. Note that 0x30 is an invalid
|
|
* value for these bits and should not occur in practice; valid heights
|
|
* are 0x00=224, 0x10=240, and (for PAL) 0x20=256. */
|
|
disp_height = 224 + (TVMD & 0x30);
|
|
if ((TVMD & 0xC0) == 0xC0) {
|
|
disp_height *= 2; // Interlaced mode
|
|
}
|
|
|
|
/* Hi-res or interlaced displays won't fit on the PSP screen, so cut
|
|
* everything in half when using them. */
|
|
disp_xscale = (disp_width > 352);
|
|
disp_yscale = (disp_height > 256);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* psp_vdp2_set_priority_{NBG[0-3],RBG0}: Set the priority of the given
|
|
* background graphics layer.
|
|
*
|
|
* [Parameters]
|
|
* priority: Priority to set
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void FASTCALL psp_vdp2_set_priority_NBG0(int priority)
|
|
{
|
|
bg_priority[BG_NBG0] = priority;
|
|
}
|
|
|
|
static void FASTCALL psp_vdp2_set_priority_NBG1(int priority)
|
|
{
|
|
bg_priority[BG_NBG1] = priority;
|
|
}
|
|
|
|
static void FASTCALL psp_vdp2_set_priority_NBG2(int priority)
|
|
{
|
|
bg_priority[BG_NBG2] = priority;
|
|
}
|
|
|
|
static void FASTCALL psp_vdp2_set_priority_NBG3(int priority)
|
|
{
|
|
bg_priority[BG_NBG3] = priority;
|
|
}
|
|
|
|
static void FASTCALL psp_vdp2_set_priority_RBG0(int priority)
|
|
{
|
|
bg_priority[BG_RBG0] = priority;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/**************************** Local routines *****************************/
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp1_is_persistent: Return whether the given sprite drawing command
|
|
* identifies a texture in a persistently-cacheable area of VDP1 RAM.
|
|
*
|
|
* [Parameters]
|
|
* cmd: VDP1 command structure
|
|
* [Return value]
|
|
* Nonzero if texture can be persistently cached, else zero
|
|
*/
|
|
static int vdp1_is_persistent(vdp1cmd_struct *cmd)
|
|
{
|
|
const unsigned int first_page = cmd->CMDSRCA >> 9;
|
|
const unsigned int width_8 = (cmd->CMDSIZE >> 8) & 0x3F;
|
|
const unsigned int height = cmd->CMDSIZE & 0xFF;
|
|
const unsigned int last_page = (cmd->CMDSRCA + (width_8 * height)) >> 9;
|
|
unsigned int page;
|
|
for (page = first_page; page <= last_page; page++) {
|
|
if (!vdp1_page_cached[page]) {
|
|
vdp1_page_checksum[page] =
|
|
checksum_fast32((const uint32_t *)(Vdp1Ram + (page<<12)), 1024);
|
|
vdp1_page_cached[page] = 1;
|
|
}
|
|
}
|
|
if ((cmd->CMDPMOD>>3 & 7) == 1) {
|
|
page = cmd->CMDCOLR >> 9;
|
|
if (!vdp1_page_cached[page]) {
|
|
vdp1_page_checksum[page] =
|
|
checksum_fast32((const uint32_t *)(Vdp1Ram + (page<<12)), 1024);
|
|
vdp1_page_cached[page] = 1;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp1_draw_lines: Draw one or four lines based on the given VDP1 command.
|
|
*
|
|
* [Parameters]
|
|
* cmd: VDP1 command pointer
|
|
* poly: Nonzero = draw four connected lines, zero = draw a single line
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void vdp1_draw_lines(vdp1cmd_struct *cmd, int poly)
|
|
{
|
|
/* Get the line color and priority. */
|
|
// FIXME: vidogl.c suggests that the priority processing done for
|
|
// sprites and polygons is not done here; is that correct?
|
|
const uint32_t color32 = vdp1_get_cmd_color(cmd);
|
|
const int priority = Vdp2Regs->PRISA & 0x7;
|
|
|
|
/* If it's Gouraud-shaded, pick up the four endpoint colors. (Only
|
|
* the first two of these are used for single lines.) */
|
|
uint32_t color_A, color_B, color_C, color_D;
|
|
if (cmd->CMDPMOD & 4) { // Gouraud shading bit
|
|
const uint32_t alpha = color32 & 0xFF000000;
|
|
if (vdp1_rofs | vdp1_gofs | vdp1_bofs) {
|
|
unsigned int temp_A, temp_B, temp_C, temp_D;
|
|
temp_A = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 0);
|
|
temp_B = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 2);
|
|
temp_C = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 4);
|
|
temp_D = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 6);
|
|
color_A = alpha | (adjust_color_16_32(temp_A, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
color_B = alpha | (adjust_color_16_32(temp_B, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
color_C = alpha | (adjust_color_16_32(temp_C, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
color_D = alpha | (adjust_color_16_32(temp_D, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
} else {
|
|
unsigned int temp_A, temp_B, temp_C, temp_D;
|
|
temp_A = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 0);
|
|
temp_B = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 2);
|
|
temp_C = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 4);
|
|
temp_D = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + 6);
|
|
color_A = alpha | (temp_A & 0x7C00) << 9
|
|
| (temp_A & 0x03E0) << 6
|
|
| (temp_A & 0x001F) << 3;
|
|
color_B = alpha | (temp_B & 0x7C00) << 9
|
|
| (temp_B & 0x03E0) << 6
|
|
| (temp_B & 0x001F) << 3;
|
|
color_C = alpha | (temp_C & 0x7C00) << 9
|
|
| (temp_C & 0x03E0) << 6
|
|
| (temp_C & 0x001F) << 3;
|
|
color_D = alpha | (temp_D & 0x7C00) << 9
|
|
| (temp_D & 0x03E0) << 6
|
|
| (temp_D & 0x001F) << 3;
|
|
}
|
|
} else {
|
|
color_A = color_B = color_C = color_D = color32;
|
|
}
|
|
|
|
/* Set up the vertex array. */
|
|
int nvertices = poly ? 5 : 2;
|
|
struct {uint32_t color; int16_t x, y, z, pad;} *vertices;
|
|
vertices = pspGuGetMemoryMerge(sizeof(*vertices) * nvertices);
|
|
vertices[0].color = color_A;
|
|
vertices[0].x = (cmd->CMDXA + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[0].y = (cmd->CMDYA + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[0].z = 0;
|
|
vertices[1].color = color_B;
|
|
vertices[1].x = (cmd->CMDXB + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[1].y = (cmd->CMDYB + Vdp1Regs->localY) >> disp_xscale;
|
|
vertices[1].z = 0;
|
|
if (poly) {
|
|
vertices[2].color = color_C;
|
|
vertices[2].x = (cmd->CMDXC + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[2].y = (cmd->CMDYC + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[2].z = 0;
|
|
vertices[3].color = color_D;
|
|
vertices[3].x = (cmd->CMDXD + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[3].y = (cmd->CMDYD + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[3].z = 0;
|
|
vertices[4] = vertices[0];
|
|
}
|
|
|
|
/* Queue the line(s). */
|
|
vdp1_queue_render(priority, 0, GU_LINE_STRIP,
|
|
GU_COLOR_8888 | GU_VERTEX_16BIT | GU_TRANSFORM_2D,
|
|
nvertices, NULL, vertices);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp1_draw_quad: Draw a quadrilateral based on the given VDP1 command.
|
|
*
|
|
* [Parameters]
|
|
* cmd: VDP1 command pointer
|
|
* textured: Nonzero if the quadrilateral is textured (i.e. a sprite)
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void vdp1_draw_quad(vdp1cmd_struct *cmd, int textured)
|
|
{
|
|
/* Get the width, height, and flip arguments for sprites (unused for
|
|
* untextured polygons). */
|
|
|
|
int width, height;
|
|
unsigned int gouraud_flip = 0; // XOR bitmask for Gouraud color addresses
|
|
if (textured) {
|
|
width = ((cmd->CMDSIZE >> 8) & 0x3F) * 8;
|
|
height = cmd->CMDSIZE & 0xFF;
|
|
if (width == 0 || height == 0) {
|
|
return;
|
|
}
|
|
/* If flipping is specified, swap the relevant coordinates in the
|
|
* "cmd" structure; this helps avoid texture glitches when the
|
|
* vertex order of a texture is changed (e.g. Panzer Dragoon Saga).
|
|
* We use inline assembly so we can load and store in 32-bit units
|
|
* without GCC complaining about strict aliasing violations. */
|
|
switch (cmd->CMDCTRL & 0x30) {
|
|
case 0x10: { // Flip horizontally
|
|
gouraud_flip = 2;
|
|
uint32_t tempA, tempB, tempC, tempD;
|
|
asm(".set push; .set noreorder\n"
|
|
"lw %[tempA], 12(%[cmd])\n"
|
|
"lw %[tempB], 16(%[cmd])\n"
|
|
"lw %[tempC], 20(%[cmd])\n"
|
|
"lw %[tempD], 24(%[cmd])\n"
|
|
"sw %[tempA], 16(%[cmd])\n"
|
|
"sw %[tempB], 12(%[cmd])\n"
|
|
"sw %[tempC], 24(%[cmd])\n"
|
|
"sw %[tempD], 20(%[cmd])\n"
|
|
".set pop"
|
|
: [tempA] "=&r" (tempA), [tempB] "=&r" (tempB),
|
|
[tempC] "=&r" (tempC), [tempD] "=&r" (tempD),
|
|
"=m" (cmd->CMDXA), "=m" (cmd->CMDYA), "=m" (cmd->CMDXB),
|
|
"=m" (cmd->CMDYB), "=m" (cmd->CMDXC), "=m" (cmd->CMDYC),
|
|
"=m" (cmd->CMDXD), "=m" (cmd->CMDYD)
|
|
: [cmd] "r" (cmd)
|
|
);
|
|
break;
|
|
} // case 0x10
|
|
case 0x20: { // Flip vertically
|
|
gouraud_flip = 6;
|
|
uint32_t tempA, tempB, tempC, tempD;
|
|
asm(".set push; .set noreorder\n"
|
|
"lw %[tempA], 12(%[cmd])\n"
|
|
"lw %[tempB], 16(%[cmd])\n"
|
|
"lw %[tempC], 20(%[cmd])\n"
|
|
"lw %[tempD], 24(%[cmd])\n"
|
|
"sw %[tempA], 24(%[cmd])\n"
|
|
"sw %[tempB], 20(%[cmd])\n"
|
|
"sw %[tempC], 16(%[cmd])\n"
|
|
"sw %[tempD], 12(%[cmd])\n"
|
|
".set pop"
|
|
: [tempA] "=&r" (tempA), [tempB] "=&r" (tempB),
|
|
[tempC] "=&r" (tempC), [tempD] "=&r" (tempD),
|
|
"=m" (cmd->CMDXA), "=m" (cmd->CMDYA), "=m" (cmd->CMDXB),
|
|
"=m" (cmd->CMDYB), "=m" (cmd->CMDXC), "=m" (cmd->CMDYC),
|
|
"=m" (cmd->CMDXD), "=m" (cmd->CMDYD)
|
|
: [cmd] "r" (cmd)
|
|
);
|
|
break;
|
|
} // case 0x20
|
|
case 0x30: { // Flip horizontally and vertically
|
|
gouraud_flip = 4;
|
|
uint32_t tempA, tempB, tempC, tempD;
|
|
asm(".set push; .set noreorder\n"
|
|
"lw %[tempA], 12(%[cmd])\n"
|
|
"lw %[tempB], 16(%[cmd])\n"
|
|
"lw %[tempC], 20(%[cmd])\n"
|
|
"lw %[tempD], 24(%[cmd])\n"
|
|
"sw %[tempA], 20(%[cmd])\n"
|
|
"sw %[tempB], 24(%[cmd])\n"
|
|
"sw %[tempC], 12(%[cmd])\n"
|
|
"sw %[tempD], 16(%[cmd])\n"
|
|
".set pop"
|
|
: [tempA] "=&r" (tempA), [tempB] "=&r" (tempB),
|
|
[tempC] "=&r" (tempC), [tempD] "=&r" (tempD),
|
|
"=m" (cmd->CMDXA), "=m" (cmd->CMDYA), "=m" (cmd->CMDXB),
|
|
"=m" (cmd->CMDYB), "=m" (cmd->CMDXC), "=m" (cmd->CMDYC),
|
|
"=m" (cmd->CMDXD), "=m" (cmd->CMDYD)
|
|
: [cmd] "r" (cmd)
|
|
);
|
|
break;
|
|
} // case 0x30
|
|
} // switch (cmd->CMDCTRL & 0x30)
|
|
} else {
|
|
width = height = 0;
|
|
}
|
|
|
|
|
|
/* Get the polygon color and priority, and load the texture if it's
|
|
* a sprite. */
|
|
|
|
int priority, sprite_alpha;
|
|
uint32_t color32 = vdp1_get_cmd_color_pri(cmd, textured, &priority);
|
|
uint32_t texture_key;
|
|
if (textured) {
|
|
texture_key = vdp1_cache_sprite_texture(cmd, width, height,
|
|
&priority, &sprite_alpha);
|
|
if (UNLIKELY(!texture_key)) {
|
|
DMSG("WARNING: failed to cache texture for A=(%d,%d) B=(%d,%d)"
|
|
" C=(%d,%d) D=(%d,%d)",
|
|
cmd->CMDXA + Vdp1Regs->localX, cmd->CMDYA + Vdp1Regs->localY,
|
|
cmd->CMDXB + Vdp1Regs->localX, cmd->CMDYB + Vdp1Regs->localY,
|
|
cmd->CMDXC + Vdp1Regs->localX, cmd->CMDYC + Vdp1Regs->localY,
|
|
cmd->CMDXD + Vdp1Regs->localX, cmd->CMDYD + Vdp1Regs->localY);
|
|
}
|
|
/* Convert alpha to 0-255 */
|
|
sprite_alpha = (sprite_alpha << 3) | (sprite_alpha >> 2);
|
|
} else {
|
|
texture_key = 0;
|
|
sprite_alpha = 0xFF;
|
|
}
|
|
|
|
/* Apply alpha depending on the color calculation settings. */
|
|
|
|
if (Vdp2Regs->CCCTL & 0x40) {
|
|
const unsigned int ref_priority = Vdp2Regs->SPCTL>>8 & 0x7;
|
|
switch (Vdp2Regs->SPCTL>>12 & 0x3) {
|
|
case 0:
|
|
if (priority <= ref_priority) {
|
|
color32 = (sprite_alpha << 24) | (color32 & 0x00FFFFFF);
|
|
}
|
|
break;
|
|
case 1:
|
|
if (priority == ref_priority) {
|
|
color32 = (sprite_alpha << 24) | (color32 & 0x00FFFFFF);
|
|
}
|
|
break;
|
|
case 2:
|
|
if (priority >= ref_priority) {
|
|
color32 = (sprite_alpha << 24) | (color32 & 0x00FFFFFF);
|
|
}
|
|
break;
|
|
case 3:
|
|
/* Alpha blending enabled based on high bit of color value
|
|
* (not supported in this renderer) */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* We don't support mesh shading; treat it as half-alpha instead. */
|
|
|
|
if (cmd->CMDPMOD & 0x100) { // Mesh shading bit
|
|
const unsigned int alpha = color32 >> 24;
|
|
color32 = ((alpha+1)/2) << 24 | (color32 & 0x00FFFFFF);
|
|
}
|
|
|
|
/* If it's a Gouraud-shaded polygon, pick up the four corner colors. */
|
|
|
|
uint32_t color_A, color_B, color_C, color_D;
|
|
if (cmd->CMDPMOD & 4) { // Gouraud shading bit
|
|
const uint32_t alpha = color32 & 0xFF000000;
|
|
if (vdp1_rofs | vdp1_gofs | vdp1_bofs) {
|
|
unsigned int temp_A, temp_B, temp_C, temp_D;
|
|
temp_A = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (0^gouraud_flip));
|
|
temp_B = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (2^gouraud_flip));
|
|
temp_C = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (4^gouraud_flip));
|
|
temp_D = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (6^gouraud_flip));
|
|
color_A = alpha | (adjust_color_16_32(temp_A, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
color_B = alpha | (adjust_color_16_32(temp_B, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
color_C = alpha | (adjust_color_16_32(temp_C, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
color_D = alpha | (adjust_color_16_32(temp_D, vdp1_rofs, vdp1_gofs,
|
|
vdp1_bofs) & 0x00FFFFFF);
|
|
} else {
|
|
unsigned int temp_A, temp_B, temp_C, temp_D;
|
|
temp_A = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (0^gouraud_flip));
|
|
temp_B = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (2^gouraud_flip));
|
|
temp_C = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (4^gouraud_flip));
|
|
temp_D = T1ReadWord(Vdp1Ram, (cmd->CMDGRDA<<3) + (6^gouraud_flip));
|
|
color_A = alpha | (temp_A & 0x7C00) << 9
|
|
| (temp_A & 0x03E0) << 6
|
|
| (temp_A & 0x001F) << 3;
|
|
color_B = alpha | (temp_B & 0x7C00) << 9
|
|
| (temp_B & 0x03E0) << 6
|
|
| (temp_B & 0x001F) << 3;
|
|
color_C = alpha | (temp_C & 0x7C00) << 9
|
|
| (temp_C & 0x03E0) << 6
|
|
| (temp_C & 0x001F) << 3;
|
|
color_D = alpha | (temp_D & 0x7C00) << 9
|
|
| (temp_D & 0x03E0) << 6
|
|
| (temp_D & 0x001F) << 3;
|
|
}
|
|
} else {
|
|
color_A = color_B = color_C = color_D = color32;
|
|
}
|
|
|
|
/* Set up the vertex array using a strip of 2 triangles. The Saturn
|
|
* coordinate order is A,B,C,D clockwise around the texture, so we flip
|
|
* around C and D in our vertex array. For simplicity, we assign both
|
|
* the color and U/V coordinates regardless of whether the polygon is
|
|
* textured or not; the GE is fast enough that it can handle all the
|
|
* processing in time. */
|
|
|
|
struct {int16_t u, v; uint32_t color; int16_t x, y, z, pad;} *vertices;
|
|
vertices = pspGuGetMemoryMerge(sizeof(*vertices) * 4);
|
|
vertices[0].u = 0;
|
|
vertices[0].v = 0;
|
|
vertices[0].color = color_A;
|
|
vertices[0].x = (cmd->CMDXA + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[0].y = (cmd->CMDYA + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[0].z = 0;
|
|
vertices[1].u = width;
|
|
vertices[1].v = 0;
|
|
vertices[1].color = color_B;
|
|
vertices[1].x = (cmd->CMDXB + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[1].y = (cmd->CMDYB + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[1].z = 0;
|
|
vertices[2].u = 0;
|
|
vertices[2].v = height;
|
|
vertices[2].color = color_D;
|
|
vertices[2].x = (cmd->CMDXD + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[2].y = (cmd->CMDYD + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[2].z = 0;
|
|
vertices[3].u = width;
|
|
vertices[3].v = height;
|
|
vertices[3].color = color_C;
|
|
vertices[3].x = (cmd->CMDXC + Vdp1Regs->localX) >> disp_xscale;
|
|
vertices[3].y = (cmd->CMDYC + Vdp1Regs->localY) >> disp_yscale;
|
|
vertices[3].z = 0;
|
|
|
|
/* Queue the draw operation. */
|
|
|
|
vdp1_queue_render(priority, texture_key,
|
|
GU_TRIANGLE_STRIP, GU_TEXTURE_16BIT | GU_COLOR_8888
|
|
| GU_VERTEX_16BIT | GU_TRANSFORM_2D,
|
|
4, NULL, vertices);
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp1_convert_color: Convert a VDP1 16-bit color value and pixel mode to
|
|
* a 32-bit color value. Helper function for vdp1_get_cmd_color() and
|
|
* vdp1_get_cmd_color_pri().
|
|
*
|
|
* [Parameters]
|
|
* color16: 16-bit color value
|
|
* textured: Nonzero if a textured polygon command, else zero
|
|
* CMDPMOD: Value of CMDPMOD field in VDP1 command
|
|
* [Return value]
|
|
* 32-bit color value
|
|
*/
|
|
static uint32_t vdp1_convert_color(uint16_t color16, int textured,
|
|
unsigned int CMDPMOD)
|
|
{
|
|
uint32_t color32;
|
|
if (textured) {
|
|
color32 = 0xFFFFFF;
|
|
} else if (color16 == 0) {
|
|
color32 = adjust_color_16_32(0x0000, vdp1_rofs, vdp1_gofs, vdp1_bofs);
|
|
return color32 & 0x00FFFFFF; // Transparent regardless of CMDPMOD
|
|
} else if (color16 & 0x8000) {
|
|
color32 = adjust_color_16_32(color16, vdp1_rofs, vdp1_gofs, vdp1_bofs);
|
|
} else {
|
|
color32 = adjust_color_32_32(global_clut_32[color16 & 0x7FF],
|
|
vdp1_rofs, vdp1_gofs, vdp1_bofs);
|
|
}
|
|
|
|
switch (CMDPMOD & 7) {
|
|
default: // Impossible, but avoid a "function may not return" warning
|
|
case 1: // Shadow
|
|
return 0x80000000;
|
|
case 4 ... 7: // Gouraud shading (handled separately)
|
|
case 0: // Replace
|
|
return 0xFF000000 | color32;
|
|
case 2: // 50% luminance
|
|
/* Clever, quick way to divide each component by 2 in one step
|
|
* (borrowed from vidsoft.c) */
|
|
return 0xFF000000 | ((color32 & 0xFEFEFE) >> 1);
|
|
case 3: // 50% transparency
|
|
return 0x80000000 | color32;
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* vdp1_get_cmd_color: Return the 32-bit color value specified by a VDP1
|
|
* line command.
|
|
*
|
|
* [Parameters]
|
|
* cmd: VDP1 command pointer
|
|
* [Return value]
|
|
* 32-bit color value
|
|
*/
|
|
static uint32_t vdp1_get_cmd_color(vdp1cmd_struct *cmd)
|
|
{
|
|
return vdp1_convert_color(cmd->CMDCOLR, 0, cmd->CMDPMOD);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* vdp1_get_cmd_color_pri: Return the 32-bit color value and priority
|
|
* specified by a VDP1 polygon command.
|
|
*
|
|
* [Parameters]
|
|
* cmd: VDP1 command pointer
|
|
* textured: Nonzero if the polygon is textured, else zero
|
|
* priority_ret: Pointer to variable to receive priority value
|
|
* [Return value]
|
|
* 32-bit color value
|
|
*/
|
|
static uint32_t vdp1_get_cmd_color_pri(vdp1cmd_struct *cmd, int textured,
|
|
int *priority_ret)
|
|
{
|
|
uint16_t color16 = cmd->CMDCOLR;
|
|
if (cmd->CMDCOLR & 0x8000) {
|
|
*priority_ret = Vdp2Regs->PRISA & 7;
|
|
} else {
|
|
*priority_ret = 0; // Default if not set by SPCTL
|
|
int alpha_unused; // FIXME: is this used by non-sprite quads as well?
|
|
vdp1_process_sprite_color(color16, priority_ret, &alpha_unused);
|
|
}
|
|
return vdp1_convert_color(color16, textured, cmd->CMDPMOD);
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* vdp1_process_sprite_color: Return the color index mask, priority index,
|
|
* and alpha (color calculation) index selected by the given color register
|
|
* value and the VDP2 SPCTL register.
|
|
*
|
|
* [Parameters]
|
|
* color16: 16-bit color register value
|
|
* priority_ret: Pointer to variable to receive priority value
|
|
* [Return value]
|
|
* Mask to apply to CMDCOLR register
|
|
*/
|
|
static uint16_t vdp1_process_sprite_color(uint16_t color16, int *priority_ret,
|
|
int *alpha_ret)
|
|
{
|
|
static const uint8_t priority_shift[16] =
|
|
{ 14, 13, 14, 13, 13, 12, 12, 12, 7, 7, 6, 0, 7, 7, 6, 0 };
|
|
static const uint8_t priority_mask[16] =
|
|
{ 3, 7, 1, 3, 3, 7, 7, 7, 1, 1, 3, 0, 1, 1, 3, 0 };
|
|
static const uint8_t alpha_shift[16] =
|
|
{ 11, 11, 11, 11, 10, 11, 10, 9, 0, 6, 0, 6, 0, 6, 0, 6 };
|
|
static const uint8_t alpha_mask[16] =
|
|
{ 7, 3, 7, 3, 7, 1, 3, 7, 0, 1, 0, 3, 0, 1, 0, 3 };
|
|
static const uint16_t color_mask[16] =
|
|
{ 0x7FF, 0x7FF, 0x7FF, 0x7FF, 0x3FF, 0x7FF, 0x3FF, 0x1FF,
|
|
0x7F, 0x3F, 0x3F, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF };
|
|
|
|
const unsigned int type = Vdp2Regs->SPCTL & 0xF;
|
|
*priority_ret = (color16 >> priority_shift[type]) & priority_mask[type];
|
|
*alpha_ret = (color16 >> alpha_shift[type]) & alpha_mask[type];
|
|
return color_mask[type];
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp1_cache_sprite_texture: Cache the sprite texture designated by the
|
|
* given VDP1 command.
|
|
*
|
|
* [Parameters]
|
|
* cmd: VDP1 command pointer
|
|
* width: Sprite width (pixels; passed in to avoid recomputation)
|
|
* height: Sprite height (pixels; passed in to avoid recomputation)
|
|
* priority_ret: Pointer to variable to receive priority value
|
|
* alpha_ret: Pointer to variable to receive alpha value (0-31)
|
|
* [Return value]
|
|
* Cached texture key, or zero on error
|
|
*/
|
|
static uint32_t vdp1_cache_sprite_texture(
|
|
vdp1cmd_struct *cmd, int width, int height, int *priority_ret,
|
|
int *alpha_ret)
|
|
{
|
|
uint16_t pixel_mask = 0xFFFF;
|
|
int pri_reg = 0, alpha_reg = 0; // Default value
|
|
|
|
int is_indexed = 1;
|
|
uint16_t color16 = cmd->CMDCOLR;
|
|
const int pixfmt = cmd->CMDPMOD>>3 & 7;
|
|
if (pixfmt == 5) {
|
|
is_indexed = 0;
|
|
} else if (pixfmt == 1) {
|
|
/* Indirect T4 texture; see whether the first pixel references
|
|
* color RAM or uses raw RGB values. */
|
|
const uint32_t addr = cmd->CMDSRCA << 3;
|
|
const uint8_t pixel = T1ReadByte(Vdp1Ram, addr) >> 4;
|
|
const uint32_t colortable = cmd->CMDCOLR << 3;
|
|
const uint16_t value = T1ReadWord(Vdp1Ram, colortable + pixel*2);
|
|
if (value & 0x8000) {
|
|
is_indexed = 0;
|
|
} else {
|
|
color16 = value;
|
|
}
|
|
}
|
|
if (is_indexed) {
|
|
pixel_mask = vdp1_process_sprite_color(color16, &pri_reg, &alpha_reg);
|
|
}
|
|
|
|
*priority_ret = ((uint8_t *)&Vdp2Regs->PRISA)[pri_reg] & 0x7;
|
|
*alpha_ret = 0x1F - (((uint8_t *)&Vdp2Regs->CCRSA)[alpha_reg] & 0x1F);
|
|
|
|
/* Cache the texture data and return the key. */
|
|
|
|
return texcache_cache_sprite(cmd, pixel_mask, width, height,
|
|
vdp1_is_persistent(cmd));
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp1_queue_render: Queue a render operation from a VDP1 command.
|
|
*
|
|
* [Parameters]
|
|
* priority: Saturn display priority (0-7)
|
|
* texture_key: Texture key for sprites, zero for untextured operations
|
|
* primitive,
|
|
* vertex_type,
|
|
* count,
|
|
* indices,
|
|
* vertices: Parameters to pass to guDrawArray()
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static inline void vdp1_queue_render(
|
|
int priority, uint32_t texture_key, int primitive,
|
|
int vertex_type, int count, const void *indices, const void *vertices)
|
|
{
|
|
/* Expand the queue if necessary. */
|
|
if (UNLIKELY(vdp1_queue[priority].len >= vdp1_queue[priority].size)) {
|
|
const int newsize = vdp1_queue[priority].size + VDP1_QUEUE_EXPAND_SIZE;
|
|
VDP1RenderData * const newqueue = realloc(vdp1_queue[priority].queue,
|
|
newsize * sizeof(*newqueue));
|
|
if (UNLIKELY(!newqueue)) {
|
|
DMSG("Failed to expand priority %d queue to %d entries",
|
|
priority, newsize);
|
|
return;
|
|
}
|
|
vdp1_queue[priority].queue = newqueue;
|
|
vdp1_queue[priority].size = newsize;
|
|
}
|
|
|
|
/* Record the data passed in. */
|
|
const int index = vdp1_queue[priority].len++;
|
|
VDP1RenderData * const entry = &vdp1_queue[priority].queue[index];
|
|
entry->texture_key = texture_key;
|
|
entry->primitive = primitive;
|
|
entry->vertex_type = vertex_type;
|
|
entry->count = count;
|
|
entry->indices = indices;
|
|
entry->vertices = vertices;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* vdp1_run_queue: Run the rendering queue for the given priority level.
|
|
*
|
|
* [Parameters]
|
|
* priority: Priority level to run
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void vdp1_run_queue(int priority)
|
|
{
|
|
int in_texture_mode; // Remember which mode we're in
|
|
VDP1RenderData *entry = vdp1_queue[priority].queue;
|
|
VDP1RenderData * const queue_top = entry + vdp1_queue[priority].len;
|
|
|
|
if (vdp1_queue[priority].len == 0) {
|
|
return; // Nothing to do
|
|
}
|
|
|
|
guShadeModel(GU_SMOOTH);
|
|
guAmbientColor(0xFFFFFFFF);
|
|
guTexFunc(GU_TFX_MODULATE, GU_TCC_RGBA);
|
|
if (config_get_smooth_textures()) {
|
|
guTexFilter(GU_LINEAR, GU_LINEAR);
|
|
}
|
|
if (entry->texture_key) {
|
|
guEnable(GU_TEXTURE_2D);
|
|
in_texture_mode = 1;
|
|
} else {
|
|
guDisable(GU_TEXTURE_2D);
|
|
in_texture_mode = 0;
|
|
}
|
|
for (; entry < queue_top; entry++) {
|
|
if (entry->texture_key) {
|
|
texcache_load_sprite(entry->texture_key);
|
|
if (!in_texture_mode) {
|
|
guEnable(GU_TEXTURE_2D);
|
|
in_texture_mode = 1;
|
|
}
|
|
} else {
|
|
if (in_texture_mode) {
|
|
guDisable(GU_TEXTURE_2D);
|
|
in_texture_mode = 0;
|
|
}
|
|
}
|
|
guDrawArray(entry->primitive, entry->vertex_type,
|
|
entry->count, entry->indices, entry->vertices);
|
|
}
|
|
if (in_texture_mode) {
|
|
guDisable(GU_TEXTURE_2D);
|
|
}
|
|
if (config_get_smooth_textures()) {
|
|
guTexFilter(GU_NEAREST, GU_NEAREST);
|
|
}
|
|
guShadeModel(GU_FLAT);
|
|
|
|
guCommit();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp2_get_color_offset: Calculate the color offsets to use for the
|
|
* specified CLOFEN/CLOFSL bit.
|
|
*
|
|
* [Parameters]
|
|
* mask: 1 << bit number to check
|
|
* rofs_ret: Pointer to variable to store red offset in
|
|
* gofs_ret: Pointer to variable to store green offset in
|
|
* bofs_ret: Pointer to variable to store blue offset in
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static inline void vdp2_get_color_offsets(uint16_t mask, int32_t *rofs_ret,
|
|
int32_t *gofs_ret, int32_t *bofs_ret)
|
|
{
|
|
if (Vdp2Regs->CLOFEN & mask) { // CoLor OFfset ENable
|
|
/* Offsets are 9-bit signed values */
|
|
if (Vdp2Regs->CLOFSL & mask) { // CoLor OFfset SeLect
|
|
*rofs_ret = ((int32_t)Vdp2Regs->COBR << 23) >> 23;
|
|
*gofs_ret = ((int32_t)Vdp2Regs->COBG << 23) >> 23;
|
|
*bofs_ret = ((int32_t)Vdp2Regs->COBB << 23) >> 23;
|
|
} else {
|
|
*rofs_ret = ((int32_t)Vdp2Regs->COAR << 23) >> 23;
|
|
*gofs_ret = ((int32_t)Vdp2Regs->COAG << 23) >> 23;
|
|
*bofs_ret = ((int32_t)Vdp2Regs->COAB << 23) >> 23;
|
|
}
|
|
} else {
|
|
/* No color offset */
|
|
*rofs_ret = *gofs_ret = *bofs_ret = 0;
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp2_draw_bg: Draw the screen background.
|
|
*
|
|
* [Parameters]
|
|
* None
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void vdp2_draw_bg(void)
|
|
{
|
|
uint32_t address = ((Vdp2Regs->BKTAU & 7) << 16 | Vdp2Regs->BKTAL) << 1;
|
|
if (!(Vdp2Regs->VRSIZE & 0x8000)) {
|
|
address &= 0x7FFFF;
|
|
}
|
|
|
|
int rofs, gofs, bofs;
|
|
vdp2_get_color_offsets(1<<6, &rofs, &gofs, &bofs);
|
|
|
|
struct {uint32_t color; int16_t x, y, z, pad;} *vertices;
|
|
|
|
if (Vdp2Regs->BKTAU & 0x8000) {
|
|
/* Distinct color for each line */
|
|
int num_vertices, y;
|
|
if (disp_height > 272) {
|
|
/* For interlaced screens, we average the colors of each two
|
|
* adjacent lines */
|
|
num_vertices = 2*(disp_height/2);
|
|
vertices = pspGuGetMemoryMerge(sizeof(*vertices) * num_vertices);
|
|
for (y = 0; y+1 < disp_height; y += 2, address += 4) {
|
|
uint16_t rgb0 = T1ReadWord(Vdp2Ram, address);
|
|
uint32_t r0 = (rgb0 & 0x001F) << 3;
|
|
uint32_t g0 = (rgb0 & 0x03E0) >> 2;
|
|
uint32_t b0 = (rgb0 & 0x7C00) >> 7;
|
|
uint16_t rgb1 = T1ReadWord(Vdp2Ram, address);
|
|
uint32_t r1 = (rgb1 & 0x001F) << 3;
|
|
uint32_t g1 = (rgb1 & 0x03E0) >> 2;
|
|
uint32_t b1 = (rgb1 & 0x7C00) >> 7;
|
|
uint32_t color = bound(((r0+r1+1)/2) + rofs, 0, 255) << 0
|
|
| bound(((g0+g1+1)/2) + gofs, 0, 255) << 8
|
|
| bound(((b0+b1+1)/2) + bofs, 0, 255) << 16
|
|
| 0xFF000000;
|
|
vertices[y+0].color = color;
|
|
vertices[y+0].x = 0;
|
|
vertices[y+0].y = y/2;
|
|
vertices[y+0].z = 0;
|
|
vertices[y+1].color = color;
|
|
vertices[y+1].x = disp_width >> disp_xscale;
|
|
vertices[y+1].y = y/2;
|
|
vertices[y+1].z = 0;
|
|
}
|
|
} else {
|
|
num_vertices = 2*disp_height;
|
|
vertices = pspGuGetMemoryMerge(sizeof(*vertices) * num_vertices);
|
|
for (y = 0; y < disp_height; y++, address += 2) {
|
|
uint16_t rgb = T1ReadWord(Vdp2Ram, address);
|
|
uint32_t r = bound(((rgb & 0x001F) << 3) + rofs, 0, 255);
|
|
uint32_t g = bound(((rgb & 0x03E0) >> 2) + gofs, 0, 255);
|
|
uint32_t b = bound(((rgb & 0x7C00) >> 7) + bofs, 0, 255);
|
|
vertices[y*2+0].color = 0xFF000000 | r | g<<8 | b<<16;
|
|
vertices[y*2+0].x = 0;
|
|
vertices[y*2+0].y = y;
|
|
vertices[y*2+0].z = 0;
|
|
vertices[y*2+1].color = 0xFF000000 | r | g<<8 | b<<16;
|
|
vertices[y*2+1].x = disp_width >> disp_xscale;
|
|
vertices[y*2+1].y = y;
|
|
vertices[y*2+1].z = 0;
|
|
}
|
|
}
|
|
guDrawArray(GU_LINES,
|
|
GU_COLOR_8888 | GU_VERTEX_16BIT | GU_TRANSFORM_2D,
|
|
num_vertices, NULL, vertices);
|
|
guCommit();
|
|
} else {
|
|
/* Single color for the whole screen */
|
|
vertices = pspGuGetMemoryMerge(sizeof(*vertices) * 2);
|
|
uint16_t rgb = T1ReadWord(Vdp2Ram, address);
|
|
uint32_t r = bound(((rgb & 0x001F) << 3) + rofs, 0, 255);
|
|
uint32_t g = bound(((rgb & 0x03E0) >> 2) + gofs, 0, 255);
|
|
uint32_t b = bound(((rgb & 0x7C00) >> 7) + bofs, 0, 255);
|
|
vertices[0].color = 0xFF000000 | r | g<<8 | b<<16;
|
|
vertices[0].x = 0;
|
|
vertices[0].y = 0;
|
|
vertices[0].z = 0;
|
|
vertices[1].color = 0xFF000000 | r | g<<8 | b<<16;
|
|
vertices[1].x = disp_width >> disp_xscale;
|
|
vertices[1].y = disp_height >> disp_yscale;
|
|
vertices[1].z = 0;
|
|
guDrawArray(GU_SPRITES,
|
|
GU_COLOR_8888 | GU_VERTEX_16BIT | GU_TRANSFORM_2D,
|
|
2, NULL, vertices);
|
|
guCommit();
|
|
}
|
|
}
|
|
|
|
/*************************************************************************/
|
|
|
|
/**
|
|
* vdp2_draw_graphics: Draw a single VDP2 background graphics layer.
|
|
*
|
|
* [Parameters]
|
|
* layer: Background graphics layer (BG_* constant)
|
|
* [Return value]
|
|
* None
|
|
*/
|
|
static void vdp2_draw_graphics(int layer)
|
|
{
|
|
vdp2draw_struct info;
|
|
clipping_struct clip[2];
|
|
|
|
/* Is this background layer enabled? */
|
|
if (!(Vdp2Regs->BGON & Vdp2External.disptoggle & (1 << layer))) {
|
|
return;
|
|
}
|
|
if (layer == BG_RBG0 && !config_get_enable_rotate()) {
|
|
return;
|
|
}
|
|
|
|
/* Check whether we should smooth the graphics. */
|
|
const int smooth_hires =
|
|
(disp_width > 352 || disp_height > 272) && config_get_smooth_hires();
|
|
|
|
/* Find out whether it's a bitmap or not. */
|
|
switch (layer) {
|
|
case BG_NBG0: info.isbitmap = Vdp2Regs->CHCTLA & 0x0002; break;
|
|
case BG_NBG1: info.isbitmap = Vdp2Regs->CHCTLA & 0x0200; break;
|
|
case BG_RBG0: info.isbitmap = Vdp2Regs->CHCTLB & 0x0200; break;
|
|
default: info.isbitmap = 0; break;
|
|
}
|
|
|
|
/* Determine color-related data. */
|
|
info.transparencyenable = !(Vdp2Regs->BGON & (0x100 << layer));
|
|
/* FIXME: specialprimode is not actually supported by the map drawing
|
|
* functions */
|
|
info.specialprimode = (Vdp2Regs->SFPRMD >> (2*layer)) & 3;
|
|
switch (layer) {
|
|
case BG_NBG0:
|
|
info.colornumber = (Vdp2Regs->CHCTLA & 0x0070) >> 4;
|
|
break;
|
|
case BG_NBG1:
|
|
info.colornumber = (Vdp2Regs->CHCTLA & 0x3000) >> 12;
|
|
break;
|
|
case BG_NBG2:
|
|
info.colornumber = (Vdp2Regs->CHCTLB & 0x0002) >> 1;
|
|
break;
|
|
case BG_NBG3:
|
|
info.colornumber = (Vdp2Regs->CHCTLB & 0x0020) >> 5;
|
|
break;
|
|
case BG_RBG0:
|
|
info.colornumber = (Vdp2Regs->CHCTLB & 0x7000) >> 12;
|
|
break;
|
|
}
|
|
if (Vdp2Regs->CCCTL & (1 << layer)) {
|
|
const uint8_t *ptr = (const uint8_t *)&Vdp2Regs->CCRNA;
|
|
info.alpha = ((~ptr[layer] & 0x1F) << 3) + 7;
|
|
} else {
|
|
info.alpha = 0xFF;
|
|
}
|
|
if (layer == BG_RBG0) {
|
|
info.coloroffset = (Vdp2Regs->CRAOFB & 7) << 8;
|
|
} else {
|
|
info.coloroffset = ((Vdp2Regs->CRAOFA >> (4*layer)) & 7) << 8;
|
|
}
|
|
vdp2_get_color_offsets(1 << layer, (int32_t *)&info.cor,
|
|
(int32_t *)&info.cog, (int32_t *)&info.cob);
|
|
|
|
/* Extract rotation information for RBG0. */
|
|
if (layer == BG_RBG0) {
|
|
switch (Vdp2Regs->RPMD & 3) {
|
|
case 0:
|
|
info.rotatenum = 0;
|
|
info.rotatemode = 0;
|
|
break;
|
|
case 1:
|
|
info.rotatenum = 1;
|
|
info.rotatemode = 0;
|
|
break;
|
|
case 2:
|
|
info.rotatenum = 0;
|
|
info.rotatemode = 1;
|
|
break;
|
|
case 3:
|
|
info.rotatenum = 0;
|
|
info.rotatemode = 2;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Determine tilemap/bitmap size and display offset. */
|
|
if (info.isbitmap) {
|
|
if (layer == BG_RBG0) {
|
|
ReadBitmapSize(&info, Vdp2Regs->CHCTLB >> 10, 0x3);
|
|
info.charaddr = ((Vdp2Regs->MPOFR >> (4*info.rotatenum)) & 7) << 17;
|
|
info.paladdr = (Vdp2Regs->BMPNB & 0x7) << 4;
|
|
} else {
|
|
ReadBitmapSize(&info, Vdp2Regs->CHCTLA >> (2 + layer*8), 0x3);
|
|
info.charaddr = ((Vdp2Regs->MPOFN >> (4*layer)) & 7) << 17;
|
|
info.paladdr = ((Vdp2Regs->BMPNA >> (8*layer)) & 7) << 4;
|
|
}
|
|
info.flipfunction = 0;
|
|
info.specialfunction = 0;
|
|
switch (layer) {
|
|
case BG_NBG0:
|
|
info.x = - ((Vdp2Regs->SCXIN0 & 0x7FF) % info.cellw);
|
|
info.y = - ((Vdp2Regs->SCYIN0 & 0x7FF) % info.cellh);
|
|
break;
|
|
case BG_NBG1:
|
|
info.x = - ((Vdp2Regs->SCXIN1 & 0x7FF) % info.cellw);
|
|
info.y = - ((Vdp2Regs->SCYIN1 & 0x7FF) % info.cellh);
|
|
break;
|
|
case BG_RBG0:
|
|
/* Transformation is handled separately; nothing to do here. */
|
|
break;
|
|
default:
|
|
DMSG("info.isbitmap set for invalid layer %d", layer);
|
|
return;
|
|
}
|
|
} else {
|
|
if (layer == BG_RBG0) {
|
|
info.mapwh = 4;
|
|
ReadPlaneSize(&info, Vdp2Regs->PLSZ >> (8 + 4*info.rotatenum));
|
|
} else {
|
|
info.mapwh = 2;
|
|
ReadPlaneSize(&info, Vdp2Regs->PLSZ >> (2*layer));
|
|
}
|
|
const int scx_mask = (512 * info.planew * info.mapwh) - 1;
|
|
const int scy_mask = (512 * info.planeh * info.mapwh) - 1;
|
|
switch (layer) {
|
|
case BG_NBG0:
|
|
info.x = - (Vdp2Regs->SCXIN0 & scx_mask);
|
|
info.y = - (Vdp2Regs->SCYIN0 & scy_mask);
|
|
ReadPatternData(&info, Vdp2Regs->PNCN0, Vdp2Regs->CHCTLA & 0x0001);
|
|
break;
|
|
case BG_NBG1:
|
|
info.x = - (Vdp2Regs->SCXIN1 & scx_mask);
|
|
info.y = - (Vdp2Regs->SCYIN1 & scy_mask);
|
|
ReadPatternData(&info, Vdp2Regs->PNCN1, Vdp2Regs->CHCTLA & 0x0100);
|
|
break;
|
|
case BG_NBG2:
|
|
info.x = - (Vdp2Regs->SCXN2 & scx_mask);
|
|
info.y = - (Vdp2Regs->SCYN2 & scy_mask);
|
|
ReadPatternData(&info, Vdp2Regs->PNCN2, Vdp2Regs->CHCTLB & 0x0001);
|
|
break;
|
|
case BG_NBG3:
|
|
info.x = - (Vdp2Regs->SCXN3 & scx_mask);
|
|
info.y = - (Vdp2Regs->SCYN3 & scy_mask);
|
|
ReadPatternData(&info, Vdp2Regs->PNCN3, Vdp2Regs->CHCTLB & 0x0010);
|
|
break;
|
|
case BG_RBG0:
|
|
ReadPatternData(&info, Vdp2Regs->PNCR, Vdp2Regs->CHCTLB & 0x0100);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Determine coordinate scaling. */
|
|
// FIXME: scaled graphics may be distorted because integers are used
|
|
// for vertex coordinates
|
|
switch (layer) {
|
|
case BG_NBG0:
|
|
info.coordincx = 65536.0f / (Vdp2Regs->ZMXN0.all & 0x7FF00 ?: 65536);
|
|
info.coordincy = 65536.0f / (Vdp2Regs->ZMYN0.all & 0x7FF00 ?: 65536);
|
|
break;
|
|
case BG_NBG1:
|
|
info.coordincx = 65536.0f / (Vdp2Regs->ZMXN1.all & 0x7FF00 ?: 65536);
|
|
info.coordincy = 65536.0f / (Vdp2Regs->ZMYN1.all & 0x7FF00 ?: 65536);
|
|
break;
|
|
default:
|
|
info.coordincx = info.coordincy = 1;
|
|
break;
|
|
}
|
|
if (disp_xscale == 1) {
|
|
info.coordincx /= 2;
|
|
}
|
|
if (disp_yscale == 1) {
|
|
info.coordincy /= 2;
|
|
}
|
|
|
|
/* Get clipping data. */
|
|
info.wctl = ((uint8_t *)&Vdp2Regs->WCTLA)[layer];
|
|
clip[0].xstart = 0; clip[0].xend = disp_width;
|
|
clip[0].ystart = 0; clip[0].yend = disp_height;
|
|
clip[1].xstart = 0; clip[1].xend = disp_width;
|
|
clip[1].ystart = 0; clip[1].yend = disp_height;
|
|
ReadWindowData(info.wctl, clip);
|
|
|
|
/* Check for a zero-size clip window, which some games seem to use to
|
|
* temporarily disable a screen. */
|
|
if (clip[0].xstart >= clip[0].xend
|
|
|| clip[0].ystart >= clip[0].yend
|
|
|| clip[1].xstart >= clip[1].xend
|
|
|| clip[1].ystart >= clip[1].yend
|
|
) {
|
|
return;
|
|
}
|
|
|
|
info.priority = bg_priority[layer];
|
|
switch (layer) {
|
|
case BG_NBG0: info.PlaneAddr = (void *)Vdp2NBG0PlaneAddr; break;
|
|
case BG_NBG1: info.PlaneAddr = (void *)Vdp2NBG1PlaneAddr; break;
|
|
case BG_NBG2: info.PlaneAddr = (void *)Vdp2NBG2PlaneAddr; break;
|
|
case BG_NBG3: info.PlaneAddr = (void *)Vdp2NBG3PlaneAddr; break;
|
|
case BG_RBG0: if (info.rotatenum == 0) {
|
|
info.PlaneAddr = (void *)Vdp2ParameterAPlaneAddr;
|
|
} else {
|
|
info.PlaneAddr = (void *)Vdp2ParameterBPlaneAddr;
|
|
}
|
|
break;
|
|
default: DMSG("No PlaneAddr for layer %d", layer); return;
|
|
}
|
|
info.patternpixelwh = 8 * info.patternwh;
|
|
info.draww = (int)((float)(disp_width >> disp_xscale) / info.coordincx);
|
|
info.drawh = (int)((float)(disp_height >> disp_yscale) / info.coordincy);
|
|
|
|
/* Set up for rendering. */
|
|
guEnable(GU_TEXTURE_2D);
|
|
guAmbientColor(info.alpha<<24 | 0xFFFFFF);
|
|
if (smooth_hires) {
|
|
guTexFilter(GU_LINEAR, GU_LINEAR);
|
|
}
|
|
|
|
/* If a custom drawing function has been specified for this layer, call
|
|
* it first. */
|
|
int custom_draw_succeeded = 0;
|
|
if (custom_draw_func[layer]) {
|
|
custom_draw_succeeded = (*custom_draw_func[layer])(&info, clip);
|
|
if (custom_draw_succeeded && layer == BG_RBG0) {
|
|
drew_slow_RBG0 = !RBG0_draw_func_is_fast;
|
|
}
|
|
}
|
|
|
|
if (!custom_draw_succeeded) {
|
|
|
|
/* Select a rendering function based on the tile layout and format. */
|
|
void (*draw_map_func)(vdp2draw_struct *info,
|
|
const clipping_struct *clip);
|
|
if (layer == BG_RBG0) {
|
|
draw_map_func = &vdp2_draw_map_rotated;
|
|
} else if (info.isbitmap) {
|
|
switch (layer) {
|
|
case BG_NBG0:
|
|
if ((Vdp2Regs->SCRCTL & 7) == 7) {
|
|
DMSG("WARNING: line scrolling not supported");
|
|
}
|
|
/* fall through */
|
|
case BG_NBG1:
|
|
if (info.colornumber == 1 && !smooth_hires) {
|
|
draw_map_func = &vdp2_draw_bitmap_t8;
|
|
} else if (info.colornumber == 4 && !smooth_hires
|
|
&& info.coordincx == 1 && info.coordincy == 1) {
|
|
draw_map_func = &vdp2_draw_bitmap_32;
|
|
} else {
|
|
draw_map_func = &vdp2_draw_bitmap;
|
|
}
|
|
break;
|
|
default:
|
|
DMSG("info.isbitmap set for invalid layer %d", layer);
|
|
return;
|
|
}
|
|
} else if (info.patternwh == 2) {
|
|
if (info.colornumber == 1 && !smooth_hires) {
|
|
draw_map_func = &vdp2_draw_map_16x16_t8;
|
|
} else {
|
|
draw_map_func = &vdp2_draw_map_16x16;
|
|
}
|
|
} else {
|
|
if (info.colornumber == 1 && !smooth_hires) {
|
|
draw_map_func = &vdp2_draw_map_8x8_t8;
|
|
} else {
|
|
draw_map_func = &vdp2_draw_map_8x8;
|
|
}
|
|
}
|
|
|
|
/* Render the graphics. */
|
|
(*draw_map_func)(&info, clip);
|
|
if (layer == BG_RBG0) {
|
|
drew_slow_RBG0 = 1;
|
|
}
|
|
|
|
} // if (!custom_draw_succeeded)
|
|
|
|
/* All done. */
|
|
if (smooth_hires) {
|
|
guTexFilter(GU_NEAREST, GU_NEAREST);
|
|
}
|
|
guAmbientColor(0xFFFFFFFF);
|
|
guDisable(GU_TEXTURE_2D);
|
|
guCommit();
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/***** Utility routines exported to background graphics drawing code *****/
|
|
/*************************************************************************/
|
|
|
|
/* Last block allocated with pspGuGetMemoryMerge() */
|
|
static void *merge_last_ptr;
|
|
static uint32_t merge_last_size;
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
|
|
/**
|
|
* pspGuGetMemoryMerge: Acquire a block of memory from the GE display
|
|
* list. Similar to sceGuGetMemory(), but if the most recent display list
|
|
* operation was also a pspGuGetMemoryMerge() call, merge the two blocks
|
|
* together to avoid long chains of jump instructions in the display list.
|
|
*
|
|
* [Parameters]
|
|
* size: Size of block to allocate, in bytes
|
|
* [Return value]
|
|
* Allocated block
|
|
*/
|
|
void *pspGuGetMemoryMerge(uint32_t size)
|
|
{
|
|
/* Make sure size is 32-bit aligned. */
|
|
size = (size + 3) & -4;
|
|
|
|
/* Start off by allocating the block normally. Ideally, we'd check
|
|
* first whether the current list pointer is immediately past the last
|
|
* block allocated, but since there's apparently no interface for
|
|
* either getting the current pointer or deleting the last instruction,
|
|
* we're out of luck and can't save the 8 bytes taken by the jump, even
|
|
* if we end up not needing it. */
|
|
void *ptr = guGetMemory(size);
|
|
|
|
/* If the pointer we got back is equal to the end of the previously
|
|
* allocated block plus 8 bytes (2 GU instructions), we can merge. */
|
|
if ((uint8_t *)ptr == (uint8_t *)merge_last_ptr + merge_last_size + 8) {
|
|
/* Make sure the instruction before the last block really is a
|
|
* jump instruction before we update it. */
|
|
uint32_t *jump_ptr = (uint32_t *)merge_last_ptr - 1;
|
|
if (*jump_ptr >> 24 == 0x08) {
|
|
void *block_end = (uint8_t *)ptr + size;
|
|
*jump_ptr = 0x08<<24 | ((uintptr_t)block_end & 0xFFFFFF);
|
|
merge_last_size = (uint8_t *)block_end - (uint8_t *)merge_last_ptr;
|
|
return ptr;
|
|
}
|
|
}
|
|
|
|
/* We couldn't merge, so reset the last-block-allocated variables and
|
|
* return the block we allocated above. */
|
|
merge_last_ptr = ptr;
|
|
merge_last_size = size;
|
|
return ptr;
|
|
}
|
|
|
|
/*************************************************************************/
|
|
/*************************************************************************/
|
|
|
|
/*
|
|
* Local variables:
|
|
* c-file-style: "stroustrup"
|
|
* c-file-offsets: ((case-label . *) (statement-case-intro . *))
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* vim: expandtab shiftwidth=4:
|
|
*/
|