mirror of https://github.com/inolen/redream.git
added support for honoring VO_CONTROL->blank_video
added support for rendering the raw framebuffer in case it is directly written to
This commit is contained in:
parent
9f67222a03
commit
046411f8f8
276
src/emulator.c
276
src/emulator.c
|
@ -45,6 +45,15 @@ enum {
|
|||
EMU_SHUTDOWN,
|
||||
EMU_WAITING,
|
||||
EMU_RUNFRAME,
|
||||
EMU_DRAWFRAME,
|
||||
EMU_ENDFRAME,
|
||||
};
|
||||
|
||||
enum {
|
||||
EMU_DRAW_INVALID,
|
||||
EMU_DRAW_PIXELS,
|
||||
EMU_DRAW_CONTEXT,
|
||||
EMU_DRAW_DISABLED,
|
||||
};
|
||||
|
||||
struct emu_texture {
|
||||
|
@ -77,15 +86,22 @@ struct emu {
|
|||
volatile int state;
|
||||
volatile unsigned frame;
|
||||
thread_t run_thread;
|
||||
mutex_t run_mutex;
|
||||
cond_t run_cond;
|
||||
mutex_t frame_mutex;
|
||||
cond_t frame_cond;
|
||||
mutex_t req_mutex;
|
||||
cond_t req_cond;
|
||||
mutex_t res_mutex;
|
||||
cond_t res_cond;
|
||||
|
||||
/* latest context from the dreamcast, ready to be rendered */
|
||||
/* latest video state pushed by the dreamcast */
|
||||
volatile int latest;
|
||||
struct {
|
||||
uint8_t pixels[PVR_FRAMEBUFFER_SIZE];
|
||||
int width;
|
||||
int height;
|
||||
} latest_fb;
|
||||
struct tr_context latest_rc;
|
||||
|
||||
/* latest context submitted to emu_start_render */
|
||||
struct ta_context *pending_ctx;
|
||||
struct tr_context pending_rc;
|
||||
unsigned pending_id;
|
||||
|
||||
/* texture cache. the dreamcast interface calls into us when new contexts are
|
||||
available to be rendered. parsing the contexts, uploading their textures to
|
||||
|
@ -236,8 +252,8 @@ static void emu_register_texture_source(struct emu *emu, union tsp tsp,
|
|||
}
|
||||
|
||||
/* mark texture source valid for the current pending frame */
|
||||
int first_registration_this_frame = entry->frame != emu->pending_id;
|
||||
entry->frame = emu->pending_id;
|
||||
int first_registration_this_frame = entry->frame != emu->frame;
|
||||
entry->frame = emu->frame;
|
||||
|
||||
/* set texture address */
|
||||
if (!entry->texture || !entry->palette) {
|
||||
|
@ -336,12 +352,29 @@ static void emu_start_tracing(struct emu *emu) {
|
|||
/*
|
||||
* dreamcast guest interface
|
||||
*/
|
||||
static void emu_vblank_in(void *userdata) {}
|
||||
static void emu_vblank_in(void *userdata, int video_disabled) {
|
||||
struct emu *emu = userdata;
|
||||
|
||||
if (emu->multi_threaded) {
|
||||
mutex_lock(emu->res_mutex);
|
||||
}
|
||||
|
||||
if (video_disabled) {
|
||||
emu->latest = EMU_DRAW_DISABLED;
|
||||
}
|
||||
|
||||
emu->state = EMU_DRAWFRAME;
|
||||
|
||||
if (emu->multi_threaded) {
|
||||
cond_signal(emu->res_cond);
|
||||
mutex_unlock(emu->res_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
static void emu_vblank_out(void *userdata) {
|
||||
struct emu *emu = userdata;
|
||||
|
||||
emu->frame++;
|
||||
emu->state = EMU_ENDFRAME;
|
||||
}
|
||||
|
||||
static void emu_finish_render(void *userdata) {
|
||||
|
@ -352,13 +385,13 @@ static void emu_finish_render(void *userdata) {
|
|||
textures, etc. during the estimated render time. however, if it hasn't
|
||||
finished, the emulation thread must be paused to avoid altering
|
||||
the yet-to-be-uploaded texture memory */
|
||||
mutex_lock(emu->frame_mutex);
|
||||
mutex_lock(emu->res_mutex);
|
||||
|
||||
/* if pending_ctx is non-NULL here, a frame is being skipped */
|
||||
emu->pending_ctx = NULL;
|
||||
cond_signal(emu->frame_cond);
|
||||
cond_signal(emu->res_cond);
|
||||
|
||||
mutex_unlock(emu->frame_mutex);
|
||||
mutex_unlock(emu->res_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,7 +401,7 @@ static void emu_start_render(void *userdata, struct ta_context *ctx) {
|
|||
/* incement internal frame number. this frame number is assigned to the each
|
||||
texture source registered to assert synchronization between the emulator
|
||||
and video thread is working as expected */
|
||||
emu->pending_id++;
|
||||
emu->frame++;
|
||||
|
||||
/* now that the video thread is sure to not be accessing the texture data,
|
||||
mark any textures dirty that were invalidated by a memory watch */
|
||||
|
@ -386,18 +419,27 @@ static void emu_start_render(void *userdata, struct ta_context *ctx) {
|
|||
|
||||
if (emu->multi_threaded) {
|
||||
/* save off context and notify video thread that it's available */
|
||||
mutex_lock(emu->frame_mutex);
|
||||
mutex_lock(emu->res_mutex);
|
||||
|
||||
emu->pending_ctx = ctx;
|
||||
cond_signal(emu->frame_cond);
|
||||
cond_signal(emu->res_cond);
|
||||
|
||||
mutex_unlock(emu->frame_mutex);
|
||||
mutex_unlock(emu->res_mutex);
|
||||
} else {
|
||||
/* convert the context immediately */
|
||||
tr_convert_context(emu->r, emu, &emu_find_texture, ctx, &emu->pending_rc);
|
||||
emu->pending_ctx = ctx;
|
||||
}
|
||||
}
|
||||
|
||||
static void emu_push_pixels(void *userdata, const uint8_t *data, int w, int h) {
|
||||
struct emu *emu = userdata;
|
||||
|
||||
emu->latest = EMU_DRAW_PIXELS;
|
||||
|
||||
memcpy(emu->latest_fb.pixels, data, w * h * 4);
|
||||
emu->latest_fb.width = w;
|
||||
emu->latest_fb.height = h;
|
||||
}
|
||||
|
||||
static void emu_push_audio(void *userdata, const int16_t *data, int frames) {
|
||||
struct emu *emu = userdata;
|
||||
audio_push(emu->host, data, frames);
|
||||
|
@ -520,52 +562,40 @@ static void emu_debug_menu(struct emu *emu) {
|
|||
/*
|
||||
* frame running logic
|
||||
*/
|
||||
static void emu_run_frame(struct emu *emu);
|
||||
static void emu_run_until_vblank(struct emu *emu);
|
||||
|
||||
static void *emu_run_thread(void *data) {
|
||||
struct emu *emu = data;
|
||||
|
||||
while (1) {
|
||||
/* wait for video thread to request a frame to be ran */
|
||||
{
|
||||
mutex_lock(emu->run_mutex);
|
||||
mutex_lock(emu->req_mutex);
|
||||
|
||||
while (emu->state == EMU_WAITING) {
|
||||
cond_wait(emu->run_cond, emu->run_mutex);
|
||||
}
|
||||
|
||||
if (emu->state == EMU_SHUTDOWN) {
|
||||
mutex_unlock(emu->run_mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
emu_run_frame(emu);
|
||||
|
||||
emu->state = EMU_WAITING;
|
||||
|
||||
mutex_unlock(emu->run_mutex);
|
||||
while (emu->state == EMU_WAITING) {
|
||||
cond_wait(emu->req_cond, emu->req_mutex);
|
||||
}
|
||||
|
||||
/* in case no context was submitted before the frame end, signal the video
|
||||
thread to continue, reusing the previous context */
|
||||
{
|
||||
mutex_lock(emu->frame_mutex);
|
||||
|
||||
cond_signal(emu->frame_cond);
|
||||
|
||||
mutex_unlock(emu->frame_mutex);
|
||||
if (emu->state == EMU_SHUTDOWN) {
|
||||
mutex_unlock(emu->req_mutex);
|
||||
break;
|
||||
}
|
||||
|
||||
emu_run_until_vblank(emu);
|
||||
|
||||
emu->state = EMU_WAITING;
|
||||
|
||||
mutex_unlock(emu->req_mutex);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void emu_run_frame(struct emu *emu) {
|
||||
static void emu_run_until_vblank(struct emu *emu) {
|
||||
const int64_t MACHINE_STEP = HZ_TO_NANO(1000);
|
||||
unsigned start_frame = emu->frame;
|
||||
|
||||
/* run up to the next vblank */
|
||||
while (emu->frame == start_frame) {
|
||||
emu->state = EMU_RUNFRAME;
|
||||
|
||||
while (emu->state == EMU_RUNFRAME || emu->state == EMU_DRAWFRAME) {
|
||||
dc_tick(emu->dc, MACHINE_STEP);
|
||||
}
|
||||
}
|
||||
|
@ -613,51 +643,92 @@ void emu_render_frame(struct emu *emu) {
|
|||
|
||||
r_viewport(emu->r, frame_x, frame_y, frame_width, frame_height);
|
||||
|
||||
/* an overview of each frames lifecycle looks like:
|
||||
|
||||
main thread | emulation thread
|
||||
---------------------------------------------------------------------------
|
||||
set EMU_RUNFRAME |
|
||||
---------------------------------------------------------------------------
|
||||
| see EMU_RUNFRAME, start running frame
|
||||
---------------------------------------------------------------------------
|
||||
wait for EMU_DRAWFRAME / or for |
|
||||
pending_ctx to be set |
|
||||
---------------------------------------------------------------------------
|
||||
| emu_start_render sets pending_ctx or
|
||||
| emu_push_pixels copies off framebuffer
|
||||
---------------------------------------------------------------------------
|
||||
convert pending_ctx if set |
|
||||
---------------------------------------------------------------------------
|
||||
| emu_vblank_in sets EMU_DRAWFRAME
|
||||
---------------------------------------------------------------------------
|
||||
see EMU_DRAWFRAME, start drawing |
|
||||
---------------------------------------------------------------------------
|
||||
| emu_vblank_out sets EMU_ENDFRAME */
|
||||
|
||||
/* ensure the emulation thread isn't still executing a previous frame */
|
||||
if (emu->multi_threaded) {
|
||||
/* tell the emulation thread to run the next frame */
|
||||
{
|
||||
mutex_lock(emu->run_mutex);
|
||||
|
||||
/* build the debug menus before running the frame, while the two threads
|
||||
are synchronized */
|
||||
emu_debug_menu(emu);
|
||||
|
||||
emu->state = EMU_RUNFRAME;
|
||||
cond_signal(emu->run_cond);
|
||||
|
||||
mutex_unlock(emu->run_mutex);
|
||||
}
|
||||
|
||||
/* wait for the emulation thread to submit a context */
|
||||
{
|
||||
mutex_lock(emu->frame_mutex);
|
||||
|
||||
while (emu->state == EMU_RUNFRAME && !emu->pending_ctx) {
|
||||
cond_wait(emu->frame_cond, emu->frame_mutex);
|
||||
}
|
||||
|
||||
/* if a context was submitted before the vblank, convert it and upload
|
||||
its textures to the render backend */
|
||||
if (emu->pending_ctx) {
|
||||
tr_convert_context(emu->r, emu, &emu_find_texture, emu->pending_ctx,
|
||||
&emu->pending_rc);
|
||||
emu->pending_ctx = NULL;
|
||||
}
|
||||
|
||||
/* unblock the emulation thread */
|
||||
mutex_unlock(emu->frame_mutex);
|
||||
}
|
||||
|
||||
/* note, the emulation thread may still be running at this point, but the
|
||||
latest context has been converted and is available in pending_rc */
|
||||
} else {
|
||||
emu_debug_menu(emu);
|
||||
|
||||
emu_run_frame(emu);
|
||||
mutex_lock(emu->req_mutex);
|
||||
mutex_unlock(emu->req_mutex);
|
||||
}
|
||||
|
||||
/* render the latest context */
|
||||
tr_render_context(emu->r, &emu->pending_rc);
|
||||
/* build the debug menus before running the frame, while the two threads
|
||||
are synchronized */
|
||||
emu_debug_menu(emu);
|
||||
|
||||
/* request a frame to be ran */
|
||||
if (emu->multi_threaded) {
|
||||
mutex_lock(emu->req_mutex);
|
||||
|
||||
emu->state = EMU_RUNFRAME;
|
||||
cond_signal(emu->req_cond);
|
||||
|
||||
mutex_unlock(emu->req_mutex);
|
||||
} else {
|
||||
emu_run_until_vblank(emu);
|
||||
}
|
||||
|
||||
/* process any context submitted during the frame */
|
||||
if (emu->multi_threaded) {
|
||||
mutex_lock(emu->res_mutex);
|
||||
|
||||
while (emu->state == EMU_RUNFRAME && !emu->pending_ctx) {
|
||||
cond_wait(emu->res_cond, emu->res_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
if (emu->pending_ctx) {
|
||||
emu->latest = EMU_DRAW_CONTEXT;
|
||||
|
||||
tr_convert_context(emu->r, emu, &emu_find_texture, emu->pending_ctx,
|
||||
&emu->latest_rc);
|
||||
emu->pending_ctx = NULL;
|
||||
}
|
||||
|
||||
if (emu->multi_threaded) {
|
||||
mutex_unlock(emu->res_mutex);
|
||||
}
|
||||
|
||||
/* wait for vblank_in */
|
||||
if (emu->multi_threaded) {
|
||||
mutex_lock(emu->res_mutex);
|
||||
|
||||
while (emu->state == EMU_RUNFRAME) {
|
||||
cond_wait(emu->res_cond, emu->res_mutex);
|
||||
}
|
||||
|
||||
mutex_unlock(emu->res_mutex);
|
||||
}
|
||||
|
||||
/* render the latest framebuffer or context */
|
||||
if (emu->latest == EMU_DRAW_PIXELS) {
|
||||
r_draw_pixels(emu->r, emu->latest_fb.pixels, 0, 0, emu->latest_fb.width,
|
||||
emu->latest_fb.height);
|
||||
} else if (emu->latest == EMU_DRAW_CONTEXT) {
|
||||
tr_render_context(emu->r, &emu->latest_rc);
|
||||
}
|
||||
|
||||
/* note, the emulation thread may still be running the code between vblank_in
|
||||
and vblank_out at this point, but there's no need to wait for it */
|
||||
}
|
||||
|
||||
int emu_load(struct emu *emu, const char *path) {
|
||||
|
@ -706,18 +777,18 @@ void emu_vid_created(struct emu *emu, struct render_backend *r) {
|
|||
void emu_destroy(struct emu *emu) {
|
||||
/* shutdown the emulation thread */
|
||||
if (emu->multi_threaded) {
|
||||
mutex_lock(emu->run_mutex);
|
||||
mutex_lock(emu->req_mutex);
|
||||
emu->state = EMU_SHUTDOWN;
|
||||
cond_signal(emu->run_cond);
|
||||
mutex_unlock(emu->run_mutex);
|
||||
cond_signal(emu->req_cond);
|
||||
mutex_unlock(emu->req_mutex);
|
||||
|
||||
void *result;
|
||||
thread_join(emu->run_thread, &result);
|
||||
|
||||
mutex_destroy(emu->run_mutex);
|
||||
cond_destroy(emu->run_cond);
|
||||
mutex_destroy(emu->frame_mutex);
|
||||
cond_destroy(emu->frame_cond);
|
||||
mutex_destroy(emu->req_mutex);
|
||||
cond_destroy(emu->req_cond);
|
||||
mutex_destroy(emu->res_mutex);
|
||||
cond_destroy(emu->res_cond);
|
||||
}
|
||||
|
||||
emu_stop_tracing(emu);
|
||||
|
@ -735,6 +806,7 @@ struct emu *emu_create(struct host *host) {
|
|||
emu->dc = dc_create();
|
||||
emu->dc->userdata = emu;
|
||||
emu->dc->push_audio = &emu_push_audio;
|
||||
emu->dc->push_pixels = &emu_push_pixels;
|
||||
emu->dc->start_render = &emu_start_render;
|
||||
emu->dc->finish_render = &emu_finish_render;
|
||||
emu->dc->vblank_in = &emu_vblank_in;
|
||||
|
@ -751,10 +823,10 @@ struct emu *emu_create(struct host *host) {
|
|||
|
||||
if (emu->multi_threaded) {
|
||||
emu->state = EMU_WAITING;
|
||||
emu->run_mutex = mutex_create();
|
||||
emu->run_cond = cond_create();
|
||||
emu->frame_mutex = mutex_create();
|
||||
emu->frame_cond = cond_create();
|
||||
emu->req_mutex = mutex_create();
|
||||
emu->req_cond = cond_create();
|
||||
emu->res_mutex = mutex_create();
|
||||
emu->res_cond = cond_create();
|
||||
|
||||
emu->run_thread = thread_create(&emu_run_thread, NULL, emu);
|
||||
CHECK_NOTNULL(emu->run_thread);
|
||||
|
|
|
@ -23,12 +23,12 @@ void dc_vblank_out(struct dreamcast *dc) {
|
|||
dc->vblank_out(dc->userdata);
|
||||
}
|
||||
|
||||
void dc_vblank_in(struct dreamcast *dc) {
|
||||
void dc_vblank_in(struct dreamcast *dc, int video_disabled) {
|
||||
if (!dc->vblank_in) {
|
||||
return;
|
||||
}
|
||||
|
||||
dc->vblank_in(dc->userdata);
|
||||
dc->vblank_in(dc->userdata, video_disabled);
|
||||
}
|
||||
|
||||
void dc_finish_render(struct dreamcast *dc) {
|
||||
|
@ -47,6 +47,14 @@ void dc_start_render(struct dreamcast *dc, struct ta_context *ctx) {
|
|||
dc->start_render(dc->userdata, ctx);
|
||||
}
|
||||
|
||||
void dc_push_pixels(struct dreamcast *dc, const uint8_t *data, int w, int h) {
|
||||
if (!dc->push_pixels) {
|
||||
return;
|
||||
}
|
||||
|
||||
dc->push_pixels(dc->userdata, data, w, h);
|
||||
}
|
||||
|
||||
void dc_push_audio(struct dreamcast *dc, const int16_t *data, int frames) {
|
||||
if (!dc->push_audio) {
|
||||
return;
|
||||
|
@ -81,29 +89,63 @@ int dc_running(struct dreamcast *dc) {
|
|||
return dc->running;
|
||||
}
|
||||
|
||||
int dc_load(struct dreamcast *dc, const char *path) {
|
||||
if (path) {
|
||||
LOG_INFO("dc_load path=%s", path);
|
||||
static int dc_load_bin(struct dreamcast *dc, const char *path) {
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (!fp) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct disc *disc = disc_create(path);
|
||||
fseek(fp, 0, SEEK_END);
|
||||
int size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (!disc) {
|
||||
LOG_WARNING("dc_load_game failed");
|
||||
return 0;
|
||||
}
|
||||
/* load to 0x0c010000 (area 3) which is where 1ST_READ.BIN is loaded to */
|
||||
uint8_t *data = memory_translate(dc->memory, "system ram", 0x00010000);
|
||||
int n = (int)fread(data, sizeof(uint8_t), size, fp);
|
||||
fclose(fp);
|
||||
|
||||
gdrom_set_disc(dc->gdrom, disc);
|
||||
} else {
|
||||
LOG_INFO("dc_load no path supplied, loading bios");
|
||||
if (n != size) {
|
||||
LOG_WARNING("failed to read %s", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* boot to bios bootstrap */
|
||||
sh4_reset(dc->sh4, 0x0c010000);
|
||||
dc_resume(dc);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int dc_load_disc(struct dreamcast *dc, const char *path) {
|
||||
struct disc *disc = disc_create(path);
|
||||
|
||||
if (!disc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* boot to bios bootstrap */
|
||||
gdrom_set_disc(dc->gdrom, disc);
|
||||
sh4_reset(dc->sh4, 0xa0000000);
|
||||
dc_resume(dc);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int dc_load(struct dreamcast *dc, const char *path) {
|
||||
if (!path) {
|
||||
LOG_INFO("dc_load no path supplied, loading bios");
|
||||
|
||||
/* boot to bios bootstrap */
|
||||
sh4_reset(dc->sh4, 0xa0000000);
|
||||
dc_resume(dc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
LOG_INFO("dc_load path=%s", path);
|
||||
|
||||
return dc_load_disc(dc, path) || dc_load_bin(dc, path);
|
||||
}
|
||||
|
||||
int dc_init(struct dreamcast *dc) {
|
||||
if (dc->debugger && !debugger_init(dc->debugger)) {
|
||||
LOG_WARNING("dc_init failed to initialize debugger");
|
||||
|
|
|
@ -133,9 +133,11 @@ struct device {
|
|||
* machine
|
||||
*/
|
||||
typedef void (*push_audio_cb)(void *, const int16_t *, int);
|
||||
typedef void (*push_pixels_cb)(void *, const uint8_t *, int, int);
|
||||
typedef void (*start_render_cb)(void *, struct ta_context *);
|
||||
typedef void (*finish_render_cb)(void *);
|
||||
typedef void (*vblank_cb)(void *);
|
||||
typedef void (*vblank_in_cb)(void *, int);
|
||||
typedef void (*vblank_out_cb)(void *);
|
||||
|
||||
struct dreamcast {
|
||||
int running;
|
||||
|
@ -162,10 +164,11 @@ struct dreamcast {
|
|||
/* client callbacks */
|
||||
void *userdata;
|
||||
push_audio_cb push_audio;
|
||||
push_pixels_cb push_pixels;
|
||||
start_render_cb start_render;
|
||||
finish_render_cb finish_render;
|
||||
vblank_cb vblank_in;
|
||||
vblank_cb vblank_out;
|
||||
vblank_in_cb vblank_in;
|
||||
vblank_out_cb vblank_out;
|
||||
};
|
||||
|
||||
struct dreamcast *dc_create();
|
||||
|
@ -202,9 +205,10 @@ void dc_input(struct dreamcast *dc, int port, int button, int16_t value);
|
|||
|
||||
/* client functionality */
|
||||
void dc_push_audio(struct dreamcast *dc, const int16_t *data, int frames);
|
||||
void dc_push_pixels(struct dreamcast *dc, const uint8_t *data, int w, int h);
|
||||
void dc_start_render(struct dreamcast *dc, struct ta_context *ctx);
|
||||
void dc_finish_render(struct dreamcast *dc);
|
||||
void dc_vblank_in(struct dreamcast *dc);
|
||||
void dc_vblank_in(struct dreamcast *dc, int video_disabled);
|
||||
void dc_vblank_out(struct dreamcast *dc);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -22,7 +22,9 @@ static void holly_ch2_dma_stop(struct holly *hl) {
|
|||
}
|
||||
|
||||
static void holly_ch2_dma(struct holly *hl) {
|
||||
/* FIXME what are SB_LMMODE0 / SB_LMMODE1 */
|
||||
/* the ta mmio handlers assume the 64-bit access path will be used */
|
||||
CHECK(*hl->SB_LMMODE0 == 0 && *hl->SB_LMMODE1 == 0);
|
||||
|
||||
struct sh4_dtr dtr = {0};
|
||||
dtr.channel = 2;
|
||||
dtr.dir = SH4_DMA_TO_ADDR;
|
||||
|
|
|
@ -7,7 +7,194 @@
|
|||
#include "guest/sh4/sh4.h"
|
||||
#include "stats.h"
|
||||
|
||||
struct reg_cb pvr_cb[PVR_NUM_REGS];
|
||||
static struct reg_cb pvr_cb[PVR_NUM_REGS];
|
||||
|
||||
/* the dreamcast has 8MB of vram, split into two 4MB banks, with two ways of
|
||||
accessing it:
|
||||
|
||||
* 64-bit access path - each 4MB bank is interleaved every 32-bits, enabling
|
||||
a 64-bit data bus to be populated from both banks in parallel
|
||||
* 32-bit access path - each 4MB bank is accessed sequentially one after the
|
||||
other
|
||||
|
||||
by default (when SB_LMMODE0/1=0) the ta will use the 64-bit access path for
|
||||
poly and texture transfers. due to this being the default for the ta, our
|
||||
internal vram layout matches the 64-bit access paths view, meaning 32-bit
|
||||
accesses will have to be converted to an interleaved address */
|
||||
static uint32_t VRAM64(uint32_t addr32) {
|
||||
const uint32_t bank_size = 0x00400000;
|
||||
uint32_t bank = addr32 & bank_size;
|
||||
uint32_t offset = addr32 & (bank_size - 1);
|
||||
return ((offset & ~0x3) << 1) | (bank >> 20) | (offset & 0x3);
|
||||
}
|
||||
|
||||
/* on the real hardware, the CORE copies it's final accumulation buffer to a
|
||||
framebuffer in texture memory, where the DVE then reads it from to produce
|
||||
the actual video output
|
||||
|
||||
when emulating, this process is skipped, and the output is instead rendered
|
||||
directly to the host's default framebuffer. this avoids several unnecessary
|
||||
copies between the gpu and cpu, and is significantly faster
|
||||
|
||||
the downside to this approach being that it doesn't work for programs such
|
||||
as the IP.BIN license screen code, which write directly to the framebuffer,
|
||||
as that memory is never read from to produce video output
|
||||
|
||||
to support these direct writes to the framebuffer, the PVR code marks each
|
||||
framebuffer during a STARTRENDER request by writing a cookie to its memory,
|
||||
and then checks for this cookie during the vblank. if the cookie doesn't
|
||||
exist, it's assumed that the framebuffer memory is dirty and the texture
|
||||
memory is copied and passed to the client to render */
|
||||
#define PVR_FB_COOKIE 0xdeadbeef
|
||||
|
||||
static int pvr_test_framebuffer(struct pvr *pvr, uint32_t addr) {
|
||||
uint32_t data = *(uint32_t *)&pvr->video_ram[VRAM64(addr)];
|
||||
return data != PVR_FB_COOKIE;
|
||||
}
|
||||
|
||||
static void pvr_mark_framebuffer(struct pvr *pvr, uint32_t addr) {
|
||||
/* don't mark framebuffers which are being used as textures */
|
||||
if (addr & 0x01000000) {
|
||||
return;
|
||||
}
|
||||
|
||||
*(uint32_t *)&pvr->video_ram[VRAM64(addr)] = PVR_FB_COOKIE;
|
||||
|
||||
/* it's not enough to just mark the starting address of this framebuffer. next
|
||||
frame, this framebuffer could be used as field 2, in which case FB_R_SOF2
|
||||
would be set to addr + line_size + line_mod */
|
||||
const uint32_t line_width[] = {320, 640};
|
||||
const uint32_t line_bpp[] = {2, 3, 4};
|
||||
const uint32_t line_scale[] = {1, 2};
|
||||
|
||||
for (int i = 0; i < ARRAY_SIZE(line_width); i++) {
|
||||
for (int j = 0; j < ARRAY_SIZE(line_bpp); j++) {
|
||||
for (int k = 0; k < ARRAY_SIZE(line_scale); k++) {
|
||||
uint32_t next_line = addr + line_width[i] * line_bpp[j] * line_scale[k];
|
||||
*(uint32_t *)&pvr->video_ram[VRAM64(next_line)] = PVR_FB_COOKIE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int pvr_update_framebuffer(struct pvr *pvr) {
|
||||
uint32_t fields[2] = {*pvr->FB_R_SOF1, *pvr->FB_R_SOF2};
|
||||
int num_fields = pvr->SPG_CONTROL->interlace ? 2 : 1;
|
||||
int field = pvr->SPG_STATUS->fieldnum;
|
||||
|
||||
if (!pvr->FB_R_CTRL->fb_enable) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* don't do anything if the framebuffer hasn't been written to */
|
||||
if (!pvr_test_framebuffer(pvr, fields[field])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* values in FB_R_SIZE are in 32-bit units */
|
||||
int line_mod = (pvr->FB_R_SIZE->mod << 2) - 4;
|
||||
int x_size = (pvr->FB_R_SIZE->x + 1) << 2;
|
||||
int y_size = (pvr->FB_R_SIZE->y + 1);
|
||||
|
||||
pvr->framebuffer_w = pvr->FB_R_SIZE->x + 1;
|
||||
pvr->framebuffer_h = pvr->FB_R_SIZE->y + 1;
|
||||
|
||||
/* final fb will be 2x height when interlacing */
|
||||
if (pvr->SPG_CONTROL->interlace) {
|
||||
pvr->framebuffer_h *= 2;
|
||||
}
|
||||
|
||||
/* convert framebuffer into a 24-bit RGB pixel buffer */
|
||||
uint8_t *dst = pvr->framebuffer;
|
||||
uint8_t *src = pvr->video_ram;
|
||||
|
||||
switch (pvr->FB_R_CTRL->fb_depth) {
|
||||
case 0:
|
||||
case 1: {
|
||||
/* FB_R_SIZE specifies x in 32-bit units, if the framebuffer is using a
|
||||
16-bit format this needs to be doubled */
|
||||
pvr->framebuffer_w *= 2;
|
||||
|
||||
for (int y = 0; y < y_size; y++) {
|
||||
for (int n = 0; n < num_fields; n++) {
|
||||
for (int x = 0; x < x_size; x += 2) {
|
||||
uint16_t rgb = *(uint16_t *)&src[VRAM64(fields[n])];
|
||||
dst[0] = (rgb & 0b1111100000000000) >> 8;
|
||||
dst[1] = (rgb & 0b0000011111100000) >> 3;
|
||||
dst[2] = (rgb & 0b0000000000011111) << 3;
|
||||
fields[n] += 2;
|
||||
dst += 3;
|
||||
}
|
||||
fields[n] += line_mod;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 2: {
|
||||
for (int y = 0; y < y_size; y++) {
|
||||
for (int n = 0; n < num_fields; n++) {
|
||||
for (int x = 0; x < x_size; x += 3) {
|
||||
uint8_t *rgb = &src[VRAM64(fields[n])];
|
||||
dst[0] = rgb[2];
|
||||
dst[1] = rgb[1];
|
||||
dst[2] = rgb[0];
|
||||
fields[n] += 3;
|
||||
dst += 3;
|
||||
}
|
||||
fields[n] += line_mod;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case 3: {
|
||||
for (int y = 0; y < y_size; y++) {
|
||||
for (int n = 0; n < num_fields; n++) {
|
||||
for (int x = 0; x < x_size; x += 4) {
|
||||
uint8_t *krgb = &src[VRAM64(fields[n])];
|
||||
dst[0] = krgb[2];
|
||||
dst[1] = krgb[1];
|
||||
dst[2] = krgb[0];
|
||||
fields[n] += 4;
|
||||
dst += 3;
|
||||
}
|
||||
fields[n] += line_mod;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
LOG_FATAL("pvr_push_framebuffer unexpected fb_depth %d",
|
||||
pvr->FB_R_CTRL->fb_depth);
|
||||
break;
|
||||
}
|
||||
|
||||
dc_push_pixels(pvr->dc, pvr->framebuffer, pvr->framebuffer_w,
|
||||
pvr->framebuffer_h);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void pvr_vblank_out(struct pvr *pvr) {
|
||||
dc_vblank_out(pvr->dc);
|
||||
}
|
||||
|
||||
static void pvr_vblank_in(struct pvr *pvr) {
|
||||
prof_counter_add(COUNTER_pvr_vblanks, 1);
|
||||
|
||||
/* if STARTRENDER wasn't written to this frame, check to see if the
|
||||
framebuffer was written to directly */
|
||||
if (!pvr->got_startrender) {
|
||||
pvr_update_framebuffer(pvr);
|
||||
} else {
|
||||
pvr->got_startrender = 0;
|
||||
}
|
||||
|
||||
/* flip field */
|
||||
if (pvr->SPG_CONTROL->interlace) {
|
||||
pvr->SPG_STATUS->fieldnum = !pvr->SPG_STATUS->fieldnum;
|
||||
} else {
|
||||
pvr->SPG_STATUS->fieldnum = 0;
|
||||
}
|
||||
|
||||
dc_vblank_in(pvr->dc, pvr->VO_CONTROL->blank_video);
|
||||
}
|
||||
|
||||
static void pvr_next_scanline(void *data) {
|
||||
struct pvr *pvr = data;
|
||||
|
@ -50,12 +237,10 @@ static void pvr_next_scanline(void *data) {
|
|||
}
|
||||
pvr->SPG_STATUS->scanline = pvr->current_line;
|
||||
|
||||
/* FIXME toggle SPG_STATUS.fieldnum on vblank? */
|
||||
if (!was_vsync && pvr->SPG_STATUS->vsync) {
|
||||
prof_counter_add(COUNTER_pvr_vblanks, 1);
|
||||
dc_vblank_in(pvr->dc);
|
||||
pvr_vblank_in(pvr);
|
||||
} else if (was_vsync && !pvr->SPG_STATUS->vsync) {
|
||||
dc_vblank_out(pvr->dc);
|
||||
pvr_vblank_out(pvr);
|
||||
}
|
||||
|
||||
/* reschedule */
|
||||
|
@ -140,83 +325,62 @@ static void pvr_palette_write(struct pvr *pvr, uint32_t addr, uint32_t data,
|
|||
WRITE_DATA(&pvr->palette_ram[addr]);
|
||||
}
|
||||
|
||||
static uint32_t MAP64(uint32_t addr) {
|
||||
/* the dreamcast has 8MB of vram, split into two 4MB banks, with two ways of
|
||||
accessing it:
|
||||
0x04000000 -> 0x047fffff, 32-bit sequential access
|
||||
0x05000000 -> 0x057fffff, 64-bit interleaved access
|
||||
|
||||
in 64-bit interleaved mode, the addresses map like so:
|
||||
0x05000000 = 0x0400000
|
||||
0x05400000 = 0x0400004
|
||||
0x05400002 = 0x0400006
|
||||
0x05000004 = 0x0400008
|
||||
0x05000006 = 0x040000a
|
||||
0x05400004 = 0x040000c
|
||||
0x05000008 = 0x0400010
|
||||
0x05400008 = 0x0400014
|
||||
0x0500000c = 0x0400018
|
||||
0x0540000c = 0x040001c */
|
||||
return (((addr & 0x003ffffc) << 1) + ((addr & 0x00400000) >> 20) +
|
||||
(addr & 0x3));
|
||||
}
|
||||
|
||||
static uint32_t pvr_vram_read(struct pvr *pvr, uint32_t addr,
|
||||
uint32_t data_mask) {
|
||||
static uint32_t pvr_vram64_read(struct pvr *pvr, uint32_t addr,
|
||||
uint32_t data_mask) {
|
||||
/* note, the video ram can't be directly accessed through fastmem, or texture
|
||||
cache invalidations will break. this is because textures cache entries
|
||||
only watch the physical video ram address, not all of its mirrors */
|
||||
return READ_DATA(&pvr->video_ram[addr]);
|
||||
}
|
||||
|
||||
static void pvr_vram_write(struct pvr *pvr, uint32_t addr, uint32_t data,
|
||||
uint32_t data_mask) {
|
||||
static void pvr_vram64_write(struct pvr *pvr, uint32_t addr, uint32_t data,
|
||||
uint32_t data_mask) {
|
||||
WRITE_DATA(&pvr->video_ram[addr]);
|
||||
}
|
||||
|
||||
static void pvr_vram_read_string(struct pvr *pvr, void *ptr, uint32_t src,
|
||||
int size) {
|
||||
static void pvr_vram64_read_string(struct pvr *pvr, void *ptr, uint32_t src,
|
||||
int size) {
|
||||
memcpy(ptr, &pvr->video_ram[src], size);
|
||||
}
|
||||
|
||||
static void pvr_vram_write_string(struct pvr *pvr, uint32_t dst, void *ptr,
|
||||
int size) {
|
||||
static void pvr_vram64_write_string(struct pvr *pvr, uint32_t dst, void *ptr,
|
||||
int size) {
|
||||
memcpy(&pvr->video_ram[dst], ptr, size);
|
||||
}
|
||||
|
||||
static uint32_t pvr_vram_interleaved_read(struct pvr *pvr, uint32_t addr,
|
||||
uint32_t data_mask) {
|
||||
addr = MAP64(addr);
|
||||
static uint32_t pvr_vram32_read(struct pvr *pvr, uint32_t addr,
|
||||
uint32_t data_mask) {
|
||||
addr = VRAM64(addr);
|
||||
return READ_DATA(&pvr->video_ram[addr]);
|
||||
}
|
||||
|
||||
static void pvr_vram_interleaved_write(struct pvr *pvr, uint32_t addr,
|
||||
uint32_t data, uint32_t data_mask) {
|
||||
addr = MAP64(addr);
|
||||
static void pvr_vram32_write(struct pvr *pvr, uint32_t addr, uint32_t data,
|
||||
uint32_t data_mask) {
|
||||
addr = VRAM64(addr);
|
||||
WRITE_DATA(&pvr->video_ram[addr]);
|
||||
}
|
||||
|
||||
static void pvr_vram_interleaved_read_string(struct pvr *pvr, void *ptr,
|
||||
uint32_t src, int size) {
|
||||
static void pvr_vram32_read_string(struct pvr *pvr, void *ptr, uint32_t src,
|
||||
int size) {
|
||||
CHECK(size % 4 == 0);
|
||||
|
||||
uint8_t *dst = ptr;
|
||||
uint8_t *end = dst + size;
|
||||
while (dst < end) {
|
||||
*(uint32_t *)dst = *(uint32_t *)&pvr->video_ram[MAP64(src)];
|
||||
*(uint32_t *)dst = *(uint32_t *)&pvr->video_ram[VRAM64(src)];
|
||||
dst += 4;
|
||||
src += 4;
|
||||
}
|
||||
}
|
||||
|
||||
static void pvr_vram_interleaved_write_string(struct pvr *pvr, uint32_t dst,
|
||||
void *ptr, int size) {
|
||||
static void pvr_vram32_write_string(struct pvr *pvr, uint32_t dst, void *ptr,
|
||||
int size) {
|
||||
CHECK(size % 4 == 0);
|
||||
|
||||
uint8_t *src = ptr;
|
||||
uint8_t *end = src + size;
|
||||
while (src < end) {
|
||||
*(uint32_t *)&pvr->video_ram[MAP64(dst)] = *(uint32_t *)src;
|
||||
*(uint32_t *)&pvr->video_ram[VRAM64(dst)] = *(uint32_t *)src;
|
||||
dst += 4;
|
||||
src += 4;
|
||||
}
|
||||
|
@ -242,6 +406,40 @@ static int pvr_init(struct device *dev) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
void pvr_video_size(struct pvr *pvr, int *video_width, int *video_height) {
|
||||
int vga_mode = !pvr->SPG_CONTROL->NTSC && !pvr->SPG_CONTROL->PAL &&
|
||||
!pvr->SPG_CONTROL->interlace;
|
||||
|
||||
if (vga_mode) {
|
||||
*video_width = 640;
|
||||
*video_height = 480;
|
||||
} else {
|
||||
*video_width = 640;
|
||||
*video_height = 240;
|
||||
}
|
||||
|
||||
if (pvr->VO_CONTROL->pixel_double) {
|
||||
*video_width /= 2;
|
||||
}
|
||||
|
||||
if (pvr->SPG_CONTROL->interlace) {
|
||||
*video_height *= 2;
|
||||
}
|
||||
|
||||
/* scale_x signals to scale the framebuffer down by half. do so by scaling
|
||||
up the width used by the projection matrix */
|
||||
if (pvr->SCALER_CTL->scale_x) {
|
||||
*video_width *= 2;
|
||||
}
|
||||
|
||||
/* scale_y is a fixed-point scaler, with 6-bits in the integer and 10-bits
|
||||
in the decimal. this scale value is ignored when used for interlacing
|
||||
which is not emulated */
|
||||
if (!pvr->SCALER_CTL->interlace) {
|
||||
*video_height = (*video_height * pvr->SCALER_CTL->scale_y) >> 10;
|
||||
}
|
||||
}
|
||||
|
||||
void pvr_destroy(struct pvr *pvr) {
|
||||
dc_destroy_device((struct device *)pvr);
|
||||
}
|
||||
|
@ -253,6 +451,49 @@ struct pvr *pvr_create(struct dreamcast *dc) {
|
|||
return pvr;
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, SOFTRESET) {
|
||||
struct pvr *pvr = dc->pvr;
|
||||
if (!(value & 0x1)) {
|
||||
return;
|
||||
}
|
||||
ta_soft_reset(pvr->ta);
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, STARTRENDER) {
|
||||
struct pvr *pvr = dc->pvr;
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
ta_start_render(pvr->ta);
|
||||
|
||||
pvr_mark_framebuffer(pvr, *pvr->FB_W_SOF1);
|
||||
pvr_mark_framebuffer(pvr, *pvr->FB_W_SOF2);
|
||||
pvr->got_startrender = 1;
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, TA_LIST_INIT) {
|
||||
struct pvr *pvr = dc->pvr;
|
||||
if (!(value & 0x80000000)) {
|
||||
return;
|
||||
}
|
||||
ta_list_init(pvr->ta);
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, TA_LIST_CONT) {
|
||||
struct pvr *pvr = dc->pvr;
|
||||
if (!(value & 0x80000000)) {
|
||||
return;
|
||||
}
|
||||
ta_list_cont(pvr->ta);
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, TA_YUV_TEX_BASE) {
|
||||
struct pvr *pvr = dc->pvr;
|
||||
pvr->TA_YUV_TEX_BASE->full = value;
|
||||
ta_yuv_init(pvr->ta);
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, SPG_LOAD) {
|
||||
struct pvr *pvr = dc->pvr;
|
||||
pvr->SPG_LOAD->full = value;
|
||||
|
@ -279,15 +520,15 @@ AM_END();
|
|||
|
||||
AM_BEGIN(struct pvr, pvr_vram_map);
|
||||
AM_RANGE(0x00000000, 0x007fffff) AM_MOUNT("video ram")
|
||||
AM_RANGE(0x00000000, 0x007fffff) AM_HANDLE("video ram sequential",
|
||||
(mmio_read_cb)&pvr_vram_read,
|
||||
(mmio_write_cb)&pvr_vram_write,
|
||||
(mmio_read_string_cb)&pvr_vram_read_string,
|
||||
(mmio_write_string_cb)&pvr_vram_write_string)
|
||||
AM_RANGE(0x01000000, 0x017fffff) AM_HANDLE("video ram interleaved",
|
||||
(mmio_read_cb)&pvr_vram_interleaved_read,
|
||||
(mmio_write_cb)&pvr_vram_interleaved_write,
|
||||
(mmio_read_string_cb)&pvr_vram_interleaved_read_string,
|
||||
(mmio_write_string_cb)&pvr_vram_interleaved_write_string)
|
||||
AM_RANGE(0x00000000, 0x007fffff) AM_HANDLE("video ram 64",
|
||||
(mmio_read_cb)&pvr_vram64_read,
|
||||
(mmio_write_cb)&pvr_vram64_write,
|
||||
(mmio_read_string_cb)&pvr_vram64_read_string,
|
||||
(mmio_write_string_cb)&pvr_vram64_write_string)
|
||||
AM_RANGE(0x01000000, 0x017fffff) AM_HANDLE("video ram 32",
|
||||
(mmio_read_cb)&pvr_vram32_read,
|
||||
(mmio_write_cb)&pvr_vram32_write,
|
||||
(mmio_read_string_cb)&pvr_vram32_read_string,
|
||||
(mmio_write_string_cb)&pvr_vram32_write_string)
|
||||
AM_END();
|
||||
/* clang-format on */
|
||||
|
|
|
@ -9,6 +9,8 @@ struct dreamcast;
|
|||
struct holly;
|
||||
struct timer;
|
||||
|
||||
#define PVR_FRAMEBUFFER_SIZE 640 * 640 * 4
|
||||
|
||||
struct pvr {
|
||||
struct device;
|
||||
uint8_t *palette_ram;
|
||||
|
@ -20,6 +22,14 @@ struct pvr {
|
|||
int line_clock;
|
||||
uint32_t current_line;
|
||||
|
||||
/* copy of deinterlaced framebuffer from texture memory */
|
||||
uint8_t framebuffer[PVR_FRAMEBUFFER_SIZE];
|
||||
int framebuffer_w;
|
||||
int framebuffer_h;
|
||||
|
||||
/* tracks if a STARTRENDER was received for the current frame */
|
||||
int got_startrender;
|
||||
|
||||
#define PVR_REG(offset, name, default, type) type *name;
|
||||
#include "guest/pvr/pvr_regs.inc"
|
||||
#undef PVR_REG
|
||||
|
@ -28,9 +38,9 @@ struct pvr {
|
|||
AM_DECLARE(pvr_reg_map);
|
||||
AM_DECLARE(pvr_vram_map);
|
||||
|
||||
extern struct reg_cb pvr_cb[PVR_NUM_REGS];
|
||||
|
||||
struct pvr *pvr_create(struct dreamcast *dc);
|
||||
void pvr_destroy(struct pvr *pvr);
|
||||
|
||||
void pvr_video_size(struct pvr *pvr, int *video_width, int *video_height);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -312,10 +312,6 @@ static int ta_param_size_raw(union pcw pcw, int vert_type) {
|
|||
}
|
||||
}
|
||||
|
||||
static void ta_soft_reset(struct ta *ta) {
|
||||
/* FIXME what are we supposed to do here? */
|
||||
}
|
||||
|
||||
/*
|
||||
* ta parameter handling
|
||||
*
|
||||
|
@ -498,37 +494,7 @@ static void ta_save_state(struct ta *ta, struct ta_context *ctx) {
|
|||
ctx->palette_fmt = pvr->PAL_RAM_CTRL->pixel_fmt;
|
||||
|
||||
/* save video resolution in order to unproject the screen space coordinates */
|
||||
int vga_mode = !pvr->SPG_CONTROL->NTSC && !pvr->SPG_CONTROL->PAL &&
|
||||
!pvr->SPG_CONTROL->interlace;
|
||||
|
||||
if (vga_mode) {
|
||||
ctx->video_width = 640;
|
||||
ctx->video_height = 480;
|
||||
} else {
|
||||
ctx->video_width = 320;
|
||||
ctx->video_height = 240;
|
||||
}
|
||||
|
||||
if (pvr->SPG_CONTROL->interlace) {
|
||||
ctx->video_height *= 2;
|
||||
}
|
||||
|
||||
if (!pvr->VO_CONTROL->pixel_double) {
|
||||
ctx->video_width *= 2;
|
||||
}
|
||||
|
||||
/* scale_x signals to scale the framebuffer down by half. do so by scaling
|
||||
up the width used by the projection matrix */
|
||||
if (pvr->SCALER_CTL->scale_x) {
|
||||
ctx->video_width *= 2;
|
||||
}
|
||||
|
||||
/* scale_y is a fixed-point scaler, with 6-bits in the integer and 10-bits
|
||||
in the decimal. this scale value is ignored when used for interlacing
|
||||
which is not emulated */
|
||||
if (!pvr->SCALER_CTL->interlace) {
|
||||
ctx->video_height = (ctx->video_height * pvr->SCALER_CTL->scale_y) >> 10;
|
||||
}
|
||||
pvr_video_size(pvr, &ctx->video_width, &ctx->video_height);
|
||||
|
||||
/* according to the hardware docs, this is the correct calculation of the
|
||||
background ISP address. however, in practice, the second TA buffer's ISP
|
||||
|
@ -578,7 +544,7 @@ static void ta_save_state(struct ta *ta, struct ta_context *ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
static void ta_finish_render(void *data) {
|
||||
static void ta_render_context_end(void *data) {
|
||||
struct ta_context *ctx = data;
|
||||
struct ta *ta = ctx->userdata;
|
||||
|
||||
|
@ -594,7 +560,7 @@ static void ta_finish_render(void *data) {
|
|||
holly_raise_interrupt(ta->holly, HOLLY_INT_PCEOTINT);
|
||||
}
|
||||
|
||||
static void ta_start_render(struct ta *ta, struct ta_context *ctx) {
|
||||
static void ta_render_context(struct ta *ta, struct ta_context *ctx) {
|
||||
prof_counter_add(COUNTER_ta_renders, 1);
|
||||
|
||||
/* remove context from pool */
|
||||
|
@ -611,7 +577,7 @@ static void ta_start_render(struct ta *ta, struct ta_context *ctx) {
|
|||
TODO figure out a heuristic involving the number of polygons rendered */
|
||||
int64_t end = INT64_C(10000000);
|
||||
ctx->userdata = ta;
|
||||
scheduler_start_timer(ta->scheduler, &ta_finish_render, ctx, end);
|
||||
scheduler_start_timer(ta->scheduler, &ta_render_context_end, ctx, end);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -622,7 +588,7 @@ static void ta_start_render(struct ta *ta, struct ta_context *ctx) {
|
|||
#define TA_MAX_MACROBLOCK_SIZE \
|
||||
MAX(TA_YUV420_MACROBLOCK_SIZE, TA_YUV422_MACROBLOCK_SIZE)
|
||||
|
||||
static void ta_yuv_init(struct ta *ta) {
|
||||
static void ta_yuv_reset(struct ta *ta) {
|
||||
struct pvr *pvr = ta->pvr;
|
||||
|
||||
/* FIXME only YUV420 -> YUV422 supported for now */
|
||||
|
@ -711,7 +677,7 @@ static void ta_yuv_process_macroblock(struct ta *ta, void *data) {
|
|||
pvr->TA_YUV_TEX_CNT->num++;
|
||||
|
||||
if ((int)pvr->TA_YUV_TEX_CNT->num >= ta->yuv_macroblock_count) {
|
||||
ta_yuv_init(ta);
|
||||
ta_yuv_reset(ta);
|
||||
|
||||
/* raise DMA end interrupt */
|
||||
holly_raise_interrupt(ta->holly, HOLLY_INT_TAYUVINT);
|
||||
|
@ -824,6 +790,36 @@ void ta_texture_info(struct ta *ta, union tsp tsp, union tcw tcw,
|
|||
*palette = *palette_size ? &ta->pvr->palette_ram[palette_addr] : NULL;
|
||||
}
|
||||
|
||||
void ta_yuv_init(struct ta *ta) {
|
||||
ta_yuv_reset(ta);
|
||||
}
|
||||
|
||||
void ta_list_cont(struct ta *ta) {
|
||||
struct ta_context *ctx =
|
||||
ta_get_context(ta, ta->pvr->TA_ISP_BASE->base_address);
|
||||
CHECK_NOTNULL(ctx);
|
||||
ta_cont_context(ta, ctx);
|
||||
ta->curr_context = ctx;
|
||||
}
|
||||
|
||||
void ta_list_init(struct ta *ta) {
|
||||
struct ta_context *ctx =
|
||||
ta_demand_context(ta, ta->pvr->TA_ISP_BASE->base_address);
|
||||
ta_init_context(ta, ctx);
|
||||
ta->curr_context = ctx;
|
||||
}
|
||||
|
||||
void ta_start_render(struct ta *ta) {
|
||||
struct ta_context *ctx =
|
||||
ta_get_context(ta, ta->pvr->PARAM_BASE->base_address);
|
||||
CHECK_NOTNULL(ctx);
|
||||
ta_render_context(ta, ctx);
|
||||
}
|
||||
|
||||
void ta_soft_reset(struct ta *ta) {
|
||||
/* FIXME what are we supposed to do here? */
|
||||
}
|
||||
|
||||
void ta_destroy(struct ta *ta) {
|
||||
dc_destroy_device((struct device *)ta);
|
||||
}
|
||||
|
@ -836,68 +832,6 @@ struct ta *ta_create(struct dreamcast *dc) {
|
|||
return ta;
|
||||
}
|
||||
|
||||
/*
|
||||
* ta mmio registers
|
||||
*/
|
||||
REG_W32(pvr_cb, SOFTRESET) {
|
||||
struct ta *ta = dc->ta;
|
||||
|
||||
if (!(value & 0x1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ta_soft_reset(ta);
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, STARTRENDER) {
|
||||
struct ta *ta = dc->ta;
|
||||
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct ta_context *ctx =
|
||||
ta_get_context(ta, ta->pvr->PARAM_BASE->base_address);
|
||||
CHECK_NOTNULL(ctx);
|
||||
ta_start_render(ta, ctx);
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, TA_LIST_INIT) {
|
||||
struct ta *ta = dc->ta;
|
||||
|
||||
if (!(value & 0x80000000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct ta_context *ctx =
|
||||
ta_demand_context(ta, ta->pvr->TA_ISP_BASE->base_address);
|
||||
ta_init_context(ta, ctx);
|
||||
ta->curr_context = ctx;
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, TA_LIST_CONT) {
|
||||
struct ta *ta = dc->ta;
|
||||
|
||||
if (!(value & 0x80000000)) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct ta_context *ctx =
|
||||
ta_get_context(ta, ta->pvr->TA_ISP_BASE->base_address);
|
||||
CHECK_NOTNULL(ctx);
|
||||
ta_cont_context(ta, ctx);
|
||||
ta->curr_context = ctx;
|
||||
}
|
||||
|
||||
REG_W32(pvr_cb, TA_YUV_TEX_BASE) {
|
||||
struct ta *ta = dc->ta;
|
||||
struct pvr *pvr = dc->pvr;
|
||||
|
||||
pvr->TA_YUV_TEX_BASE->full = value;
|
||||
|
||||
ta_yuv_init(ta);
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
AM_BEGIN(struct ta, ta_data_map);
|
||||
AM_RANGE(0x00000000, 0x007fffff) AM_HANDLE("ta poly fifo",
|
||||
|
|
|
@ -13,6 +13,11 @@ AM_DECLARE(ta_data_map);
|
|||
struct ta *ta_create(struct dreamcast *dc);
|
||||
void ta_destroy(struct ta *ta);
|
||||
|
||||
void ta_soft_reset(struct ta *ta);
|
||||
void ta_start_render(struct ta *ta);
|
||||
void ta_list_init(struct ta *ta);
|
||||
void ta_list_cont(struct ta *ta);
|
||||
void ta_yuv_init(struct ta *ta);
|
||||
void ta_texture_info(struct ta *ta, union tsp tsp, union tcw tcw,
|
||||
const uint8_t **texture, int *texture_size,
|
||||
const uint8_t **palette, int *palette_size);
|
||||
|
|
|
@ -52,17 +52,26 @@ struct texture {
|
|||
GLuint texture;
|
||||
};
|
||||
|
||||
struct viewport {
|
||||
int x, y, w, h;
|
||||
};
|
||||
|
||||
struct render_backend {
|
||||
struct host *host;
|
||||
int width, height;
|
||||
int viewport_width;
|
||||
int viewport_height;
|
||||
|
||||
/* current viewport */
|
||||
struct viewport viewport;
|
||||
|
||||
/* default assets created during intitialization */
|
||||
GLuint white_texture;
|
||||
struct shader_program ta_programs[ATTR_COUNT];
|
||||
struct shader_program ui_program;
|
||||
|
||||
/* offscreen framebuffer for blitting raw pixels */
|
||||
GLuint pixel_fbo;
|
||||
GLuint pixel_texture;
|
||||
|
||||
/* texture cache */
|
||||
struct texture textures[MAX_TEXTURES];
|
||||
|
||||
|
@ -132,6 +141,24 @@ static GLenum prim_types[] = {
|
|||
GL_LINES, /* PRIM_LINES */
|
||||
};
|
||||
|
||||
static GLuint internal_formats[] = {
|
||||
GL_NONE, /* PXL_INVALID */
|
||||
GL_RGB, /* PXL_RGB */
|
||||
GL_RGBA, /* PXL_RGBA */
|
||||
GL_RGBA, /* PXL_RGBA5551 */
|
||||
GL_RGB, /* PXL_RGB565 */
|
||||
GL_RGBA, /* PXL_RGBA4444 */
|
||||
};
|
||||
|
||||
static GLuint pixel_formats[] = {
|
||||
GL_NONE, /* PXL_INVALID */
|
||||
GL_UNSIGNED_BYTE, /* PXL_RGB */
|
||||
GL_UNSIGNED_BYTE, /* PXL_RGBA */
|
||||
GL_UNSIGNED_SHORT_5_5_5_1, /* PXL_RGBA5551 */
|
||||
GL_UNSIGNED_SHORT_5_6_5, /* PXL_RGB565 */
|
||||
GL_UNSIGNED_SHORT_4_4_4_4, /* PXL_RGBA4444 */
|
||||
};
|
||||
|
||||
static inline void r_bind_texture(struct render_backend *r,
|
||||
enum texture_map map, GLuint tex) {
|
||||
glActiveTexture(GL_TEXTURE0 + map);
|
||||
|
@ -270,6 +297,9 @@ static void r_create_shaders(struct render_backend *r) {
|
|||
static void r_destroy_textures(struct render_backend *r) {
|
||||
glDeleteTextures(1, &r->white_texture);
|
||||
|
||||
glDeleteFramebuffers(1, &r->pixel_fbo);
|
||||
glDeleteTextures(1, &r->pixel_texture);
|
||||
|
||||
for (int i = 0; i < MAX_TEXTURES; i++) {
|
||||
struct texture *tex = &r->textures[i];
|
||||
|
||||
|
@ -293,6 +323,31 @@ static void r_create_textures(struct render_backend *r) {
|
|||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
pixels);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
/* create fbo for blitting raw framebuffers to */
|
||||
glGenFramebuffers(1, &r->pixel_fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, r->pixel_fbo);
|
||||
|
||||
glGenTextures(1, &r->pixel_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, r->pixel_texture);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB,
|
||||
GL_UNSIGNED_SHORT_5_6_5, 0);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
GLenum buffers[] = {GL_COLOR_ATTACHMENT0};
|
||||
glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, r->pixel_texture,
|
||||
0);
|
||||
glDrawBuffers(ARRAY_SIZE(buffers), buffers);
|
||||
|
||||
GLenum res = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||
CHECK_EQ(res, GL_FRAMEBUFFER_COMPLETE);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
static void r_destroy_vertex_arrays(struct render_backend *r) {
|
||||
|
@ -495,13 +550,13 @@ void r_begin_ui_surfaces(struct render_backend *r,
|
|||
const uint16_t *indices, int num_indices) {
|
||||
/* setup projection matrix */
|
||||
float ortho[16];
|
||||
ortho[0] = 2.0f / (float)r->viewport_width;
|
||||
ortho[0] = 2.0f / (float)r->viewport.w;
|
||||
ortho[4] = 0.0f;
|
||||
ortho[8] = 0.0f;
|
||||
ortho[12] = -1.0f;
|
||||
|
||||
ortho[1] = 0.0f;
|
||||
ortho[5] = -2.0f / (float)r->viewport_height;
|
||||
ortho[5] = -2.0f / (float)r->viewport.h;
|
||||
ortho[9] = 0.0f;
|
||||
ortho[13] = 1.0f;
|
||||
|
||||
|
@ -611,11 +666,29 @@ void r_begin_ta_surfaces(struct render_backend *r, int video_width,
|
|||
GL_DYNAMIC_DRAW);
|
||||
}
|
||||
|
||||
void r_viewport(struct render_backend *r, int x, int y, int width, int height) {
|
||||
r->viewport_width = width;
|
||||
r->viewport_height = height;
|
||||
void r_draw_pixels(struct render_backend *r, const uint8_t *pixels, int x,
|
||||
int y, int width, int height) {
|
||||
glBindTexture(GL_TEXTURE_2D, r->pixel_texture);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
|
||||
GL_UNSIGNED_BYTE, pixels);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
glViewport(x, y, r->viewport_width, r->viewport_height);
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, r->pixel_fbo);
|
||||
glReadBuffer(GL_COLOR_ATTACHMENT0);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
glBlitFramebuffer(x, y + height, x + width, y, r->viewport.x, r->viewport.y,
|
||||
r->viewport.x + r->viewport.w,
|
||||
r->viewport.y + r->viewport.h, GL_COLOR_BUFFER_BIT,
|
||||
GL_LINEAR);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
void r_viewport(struct render_backend *r, int x, int y, int width, int height) {
|
||||
r->viewport.x = x;
|
||||
r->viewport.y = y;
|
||||
r->viewport.w = width;
|
||||
r->viewport.h = height;
|
||||
glViewport(r->viewport.x, r->viewport.y, r->viewport.w, r->viewport.h);
|
||||
}
|
||||
|
||||
void r_clear(struct render_backend *r) {
|
||||
|
@ -650,29 +723,8 @@ texture_handle_t r_create_texture(struct render_backend *r,
|
|||
}
|
||||
CHECK_LT(handle, MAX_TEXTURES);
|
||||
|
||||
GLuint internal_fmt;
|
||||
GLuint pixel_fmt;
|
||||
switch (format) {
|
||||
case PXL_RGBA:
|
||||
internal_fmt = GL_RGBA;
|
||||
pixel_fmt = GL_UNSIGNED_BYTE;
|
||||
break;
|
||||
case PXL_RGBA5551:
|
||||
internal_fmt = GL_RGBA;
|
||||
pixel_fmt = GL_UNSIGNED_SHORT_5_5_5_1;
|
||||
break;
|
||||
case PXL_RGB565:
|
||||
internal_fmt = GL_RGB;
|
||||
pixel_fmt = GL_UNSIGNED_SHORT_5_6_5;
|
||||
break;
|
||||
case PXL_RGBA4444:
|
||||
internal_fmt = GL_RGBA;
|
||||
pixel_fmt = GL_UNSIGNED_SHORT_4_4_4_4;
|
||||
break;
|
||||
default:
|
||||
LOG_FATAL("unexpected pixel format %d", format);
|
||||
break;
|
||||
}
|
||||
GLuint internal_fmt = internal_formats[format];
|
||||
GLuint pixel_fmt = pixel_formats[format];
|
||||
|
||||
struct texture *tex = &r->textures[handle];
|
||||
glGenTextures(1, &tex->texture);
|
||||
|
|
|
@ -9,6 +9,7 @@ typedef unsigned texture_handle_t;
|
|||
|
||||
enum pxl_format {
|
||||
PXL_INVALID,
|
||||
PXL_RGB,
|
||||
PXL_RGBA,
|
||||
PXL_RGBA5551,
|
||||
PXL_RGB565,
|
||||
|
@ -139,6 +140,9 @@ void r_destroy_texture(struct render_backend *r, texture_handle_t handle);
|
|||
void r_clear(struct render_backend *r);
|
||||
void r_viewport(struct render_backend *r, int x, int y, int width, int height);
|
||||
|
||||
void r_draw_pixels(struct render_backend *r, const uint8_t *pixels, int x,
|
||||
int y, int width, int height);
|
||||
|
||||
void r_begin_ta_surfaces(struct render_backend *r, int video_width,
|
||||
int video_height, const struct ta_vertex *verts,
|
||||
int num_verts, const uint16_t *indices,
|
||||
|
|
Loading…
Reference in New Issue