mirror of https://github.com/inolen/redream.git
Merge branch 'master' of https://github.com/inolen/redream
This commit is contained in:
commit
97b1913ea3
|
@ -7,6 +7,7 @@ include(CheckIncludeFiles)
|
|||
include(CheckFunctionExists)
|
||||
include(ExternalProject)
|
||||
include(SourceGroupByDir)
|
||||
include(GetGitRevisionDescription)
|
||||
|
||||
project(redream)
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
|
@ -54,6 +55,15 @@ if(BUILD_LIBRETRO)
|
|||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
#--------------------------------------------------
|
||||
# version file
|
||||
#--------------------------------------------------
|
||||
|
||||
git_describe(GIT_VERSION --tags)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/core/version.h @ONLY)
|
||||
|
||||
|
||||
#--------------------------------------------------
|
||||
# config file
|
||||
#--------------------------------------------------
|
||||
|
@ -62,7 +72,9 @@ check_include_files(strings.h HAVE_STRINGS_H)
|
|||
check_function_exists(strcasecmp HAVE_STRCASECMP)
|
||||
check_function_exists(strnlen HAVE_STRNLEN)
|
||||
check_function_exists(strnstr HAVE_STRNSTR)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/src/core/config.h)
|
||||
|
||||
list(APPEND RELIB_INCLUDES ${CMAKE_CURRENT_BINARY_DIR}/src)
|
||||
list(APPEND RELIB_DEFS HAVE_CONFIG_H)
|
||||
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
# - Returns a version string from Git
|
||||
#
|
||||
# These functions force a re-configure on each git commit so that you can
|
||||
# trust the values of the variables in your build system.
|
||||
#
|
||||
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the refspec and sha hash of the current head revision
|
||||
#
|
||||
# git_describe(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe on the source tree, and adjusting
|
||||
# the output so that it tests false if an error occurs.
|
||||
#
|
||||
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe --exact-match on the source tree,
|
||||
# and adjusting the output so that it tests false if there was no exact
|
||||
# matching tag.
|
||||
#
|
||||
# git_local_changes(<var>)
|
||||
#
|
||||
# Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes.
|
||||
# Uses the return code of "git diff-index --quiet HEAD --".
|
||||
# Does not regard untracked files.
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
if(__get_git_revision_description)
|
||||
return()
|
||||
endif()
|
||||
set(__get_git_revision_description YES)
|
||||
|
||||
# We must run the following at "include" time, not at function call time,
|
||||
# to find the path to this module rather than the path to a calling list file
|
||||
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
|
||||
|
||||
function(get_git_head_revision _refspecvar _hashvar)
|
||||
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
|
||||
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
|
||||
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
|
||||
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
|
||||
# We have reached the root directory, we are not in git
|
||||
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
endwhile()
|
||||
# check if this is a submodule
|
||||
if(NOT IS_DIRECTORY ${GIT_DIR})
|
||||
file(READ ${GIT_DIR} submodule)
|
||||
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
|
||||
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
|
||||
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
|
||||
endif()
|
||||
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
|
||||
if(NOT EXISTS "${GIT_DATA}")
|
||||
file(MAKE_DIRECTORY "${GIT_DATA}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${GIT_DIR}/HEAD")
|
||||
return()
|
||||
endif()
|
||||
set(HEAD_FILE "${GIT_DATA}/HEAD")
|
||||
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
|
||||
|
||||
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
|
||||
"${GIT_DATA}/grabRef.cmake"
|
||||
@ONLY)
|
||||
include("${GIT_DATA}/grabRef.cmake")
|
||||
|
||||
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
|
||||
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_describe _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
get_git_head_revision(refspec hash)
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
if(NOT hash)
|
||||
set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# TODO sanitize
|
||||
#if((${ARGN}" MATCHES "&&") OR
|
||||
# (ARGN MATCHES "||") OR
|
||||
# (ARGN MATCHES "\\;"))
|
||||
# message("Please report the following error to the project!")
|
||||
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
|
||||
#endif()
|
||||
|
||||
#message(STATUS "Arguments to execute_process: ${ARGN}")
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
describe
|
||||
${hash}
|
||||
${ARGN}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_get_exact_tag _var)
|
||||
git_describe(out --exact-match ${ARGN})
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_local_changes _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
get_git_head_revision(refspec hash)
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
if(NOT hash)
|
||||
set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
diff-index --quiet HEAD --
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(res EQUAL 0)
|
||||
set(${_var} "CLEAN" PARENT_SCOPE)
|
||||
else()
|
||||
set(${_var} "DIRTY" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Internal file for GetGitRevisionDescription.cmake
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
set(HEAD_HASH)
|
||||
|
||||
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
|
||||
|
||||
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
|
||||
if(HEAD_CONTENTS MATCHES "ref")
|
||||
# named branch
|
||||
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
|
||||
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
else()
|
||||
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
|
||||
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
|
||||
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
|
||||
set(HEAD_HASH "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# detached HEAD
|
||||
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
endif()
|
||||
|
||||
if(NOT HEAD_HASH)
|
||||
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
|
||||
string(STRIP "${HEAD_HASH}" HEAD_HASH)
|
||||
endif()
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef REDREAM_VERSION_H
|
||||
#define REDREAM_VERSION_H
|
||||
|
||||
#define GIT_VERSION "@GIT_VERSION@"
|
||||
|
||||
#endif
|
|
@ -50,10 +50,15 @@ enum {
|
|||
};
|
||||
|
||||
enum {
|
||||
EMU_DRAW_INVALID,
|
||||
EMU_DRAW_PIXELS,
|
||||
EMU_DRAW_CONTEXT,
|
||||
EMU_DRAW_DISABLED,
|
||||
EMU_SOURCE_NONE,
|
||||
EMU_SOURCE_CTX,
|
||||
EMU_SOURCE_PXL,
|
||||
};
|
||||
|
||||
struct emu_framebuffer {
|
||||
uint8_t data[PVR_FRAMEBUFFER_SIZE];
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct emu_texture {
|
||||
|
@ -92,13 +97,10 @@ struct emu {
|
|||
cond_t res_cond;
|
||||
|
||||
/* 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;
|
||||
volatile int vid_disabled;
|
||||
volatile int vid_source;
|
||||
struct tr_context vid_rc;
|
||||
struct emu_framebuffer vid_fb;
|
||||
|
||||
/* latest context submitted to emu_start_render */
|
||||
struct ta_context *pending_ctx;
|
||||
|
@ -350,18 +352,15 @@ static void emu_start_tracing(struct emu *emu) {
|
|||
/*
|
||||
* dreamcast guest interface
|
||||
*/
|
||||
static void emu_vblank_in(void *userdata, int video_disabled) {
|
||||
static void emu_vblank_in(void *userdata, int vid_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;
|
||||
emu->vid_disabled = vid_disabled;
|
||||
|
||||
if (emu->multi_threaded) {
|
||||
cond_signal(emu->res_cond);
|
||||
|
@ -431,11 +430,11 @@ static void emu_start_render(void *userdata, struct ta_context *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->vid_fb.data, data, w * h * 4);
|
||||
emu->vid_fb.width = w;
|
||||
emu->vid_fb.height = h;
|
||||
|
||||
memcpy(emu->latest_fb.pixels, data, w * h * 4);
|
||||
emu->latest_fb.width = w;
|
||||
emu->latest_fb.height = h;
|
||||
emu->vid_source = EMU_SOURCE_PXL;
|
||||
}
|
||||
|
||||
static void emu_push_audio(void *userdata, const int16_t *data, int frames) {
|
||||
|
@ -600,11 +599,11 @@ void emu_render_frame(struct emu *emu) {
|
|||
}
|
||||
|
||||
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->vid_rc);
|
||||
emu->pending_ctx = NULL;
|
||||
|
||||
emu->vid_source = EMU_SOURCE_CTX;
|
||||
}
|
||||
|
||||
if (emu->multi_threaded) {
|
||||
|
@ -622,12 +621,14 @@ void emu_render_frame(struct emu *emu) {
|
|||
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);
|
||||
/* render the latest video source */
|
||||
if (!emu->vid_disabled) {
|
||||
if (emu->vid_source == EMU_SOURCE_PXL) {
|
||||
r_draw_pixels(emu->r, emu->vid_fb.data, 0, 0, emu->vid_fb.width,
|
||||
emu->vid_fb.height);
|
||||
} else if (emu->vid_source == EMU_SOURCE_CTX) {
|
||||
tr_render_context(emu->r, &emu->vid_rc);
|
||||
}
|
||||
}
|
||||
|
||||
/* note, the emulation thread may still be running the code between vblank_in
|
||||
|
|
|
@ -154,7 +154,7 @@ static int dc_load_bin(struct dreamcast *dc, const char *path) {
|
|||
}
|
||||
|
||||
static int dc_load_disc(struct dreamcast *dc, const char *path) {
|
||||
struct disc *disc = disc_create(path);
|
||||
struct disc *disc = disc_create(path, 1);
|
||||
|
||||
if (!disc) {
|
||||
return 0;
|
||||
|
|
|
@ -98,7 +98,7 @@ static void cdi_destroy(struct disc *disc) {
|
|||
}
|
||||
|
||||
static int cdi_parse_track(struct disc *disc, uint32_t version,
|
||||
int *track_offset, int *leadout_fad) {
|
||||
int *track_offset, int *leadout_fad, int verbose) {
|
||||
struct cdi *cdi = (struct cdi *)disc;
|
||||
FILE *fp = cdi->fp;
|
||||
|
||||
|
@ -187,9 +187,11 @@ static int cdi_parse_track(struct disc *disc, uint32_t version,
|
|||
track->ctrl = sector_mode == 0 ? 0 : 4;
|
||||
track->file_offset = data_offset - track->fad * track->sector_size;
|
||||
|
||||
LOG_INFO("cdi_parse_track track=%d fad=%d off=%d mode=%s/%d", track->num,
|
||||
track->fad, data_offset, cdi_sector_modes[sector_mode],
|
||||
track->sector_size);
|
||||
if (verbose) {
|
||||
LOG_INFO("cdi_parse_track track=%d fad=%d off=%d mode=%s/%d", track->num,
|
||||
track->fad, data_offset, cdi_sector_modes[sector_mode],
|
||||
track->sector_size);
|
||||
}
|
||||
|
||||
*track_offset += total_len * sector_size;
|
||||
*leadout_fad = track->fad + track_len;
|
||||
|
@ -198,7 +200,7 @@ static int cdi_parse_track(struct disc *disc, uint32_t version,
|
|||
}
|
||||
|
||||
static int cdi_parse_session(struct disc *disc, uint32_t version,
|
||||
int *track_offset) {
|
||||
int *track_offset, int verbose) {
|
||||
struct cdi *cdi = (struct cdi *)disc;
|
||||
FILE *fp = cdi->fp;
|
||||
|
||||
|
@ -217,7 +219,7 @@ static int cdi_parse_session(struct disc *disc, uint32_t version,
|
|||
int leadout_fad = 0;
|
||||
|
||||
while (num_tracks--) {
|
||||
if (!cdi_parse_track(disc, version, track_offset, &leadout_fad)) {
|
||||
if (!cdi_parse_track(disc, version, track_offset, &leadout_fad, verbose)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -252,7 +254,7 @@ static int cdi_parse_session(struct disc *disc, uint32_t version,
|
|||
return 1;
|
||||
}
|
||||
|
||||
static int cdi_parse(struct disc *disc, const char *filename) {
|
||||
static int cdi_parse(struct disc *disc, const char *filename, int verbose) {
|
||||
struct cdi *cdi = (struct cdi *)disc;
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
|
@ -281,7 +283,9 @@ static int cdi_parse(struct disc *disc, const char *filename) {
|
|||
int found = 0;
|
||||
for (int i = 0; i < ARRAY_SIZE(cdi_versions); i++) {
|
||||
if (version == cdi_versions[i]) {
|
||||
LOG_INFO("cdi_parse version %s detected", cdi_version_names[i]);
|
||||
if (verbose) {
|
||||
LOG_INFO("cdi_parse version %s detected", cdi_version_names[i]);
|
||||
}
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
|
@ -308,13 +312,15 @@ static int cdi_parse(struct disc *disc, const char *filename) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
LOG_INFO("cdi_parse found %d sessions", num_sessions);
|
||||
if (verbose) {
|
||||
LOG_INFO("cdi_parse found %d sessions", num_sessions);
|
||||
}
|
||||
|
||||
/* tracks the current track's data offset from the file start */
|
||||
int track_offset = 0;
|
||||
|
||||
while (num_sessions--) {
|
||||
if (!cdi_parse_session(disc, version, &track_offset)) {
|
||||
if (!cdi_parse_session(disc, version, &track_offset, verbose)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -329,7 +335,7 @@ static int cdi_parse(struct disc *disc, const char *filename) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
struct disc *cdi_create(const char *filename) {
|
||||
struct disc *cdi_create(const char *filename, int verbose) {
|
||||
struct cdi *cdi = calloc(1, sizeof(struct cdi));
|
||||
|
||||
cdi->destroy = &cdi_destroy;
|
||||
|
@ -343,7 +349,7 @@ struct disc *cdi_create(const char *filename) {
|
|||
|
||||
struct disc *disc = (struct disc *)cdi;
|
||||
|
||||
if (!cdi_parse(disc, filename)) {
|
||||
if (!cdi_parse(disc, filename, verbose)) {
|
||||
cdi_destroy(disc);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
struct disc;
|
||||
|
||||
struct disc *cdi_create(const char *filename);
|
||||
struct disc *cdi_create(const char *filename, int verbose);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -84,7 +84,7 @@ static void chd_destroy(struct disc *disc) {
|
|||
chd_close(chd->chd);
|
||||
}
|
||||
|
||||
static int chd_parse(struct disc *disc, const char *filename) {
|
||||
static int chd_parse(struct disc *disc, const char *filename, int verbose) {
|
||||
struct chd *chd = (struct chd *)disc;
|
||||
|
||||
chd_error err = chd_open(filename, CHD_OPEN_READ, 0, &chd->chd);
|
||||
|
@ -182,8 +182,10 @@ static int chd_parse(struct disc *disc, const char *filename) {
|
|||
track->ctrl = strcmp(type, "AUDIO") == 0 ? 0 : 4;
|
||||
track->file_offset = fad - cad;
|
||||
|
||||
LOG_INFO("chd_parse '%s' track=%d fad=%d secsz=%d", tmp, track->num,
|
||||
track->fad, track->sector_size);
|
||||
if (verbose) {
|
||||
LOG_INFO("chd_parse '%s' track=%d fad=%d secsz=%d", tmp, track->num,
|
||||
track->fad, track->sector_size);
|
||||
}
|
||||
|
||||
/* chd block addresses are padded to a 4-frame boundary */
|
||||
cad += ALIGN_UP(frames, 4);
|
||||
|
@ -213,7 +215,7 @@ static int chd_parse(struct disc *disc, const char *filename) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
struct disc *chd_create(const char *filename) {
|
||||
struct disc *chd_create(const char *filename, int verbose) {
|
||||
struct chd *chd = calloc(1, sizeof(struct chd));
|
||||
|
||||
chd->destroy = &chd_destroy;
|
||||
|
@ -227,7 +229,7 @@ struct disc *chd_create(const char *filename) {
|
|||
|
||||
struct disc *disc = (struct disc *)chd;
|
||||
|
||||
if (!chd_parse(disc, filename)) {
|
||||
if (!chd_parse(disc, filename, verbose)) {
|
||||
chd_destroy(disc);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
struct disc;
|
||||
|
||||
struct disc *chd_create(const char *filename);
|
||||
struct disc *chd_create(const char *filename, int verbose);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -280,15 +280,15 @@ void disc_destroy(struct disc *disc) {
|
|||
disc->destroy(disc);
|
||||
}
|
||||
|
||||
struct disc *disc_create(const char *filename) {
|
||||
struct disc *disc_create(const char *filename, int verbose) {
|
||||
struct disc *disc = NULL;
|
||||
|
||||
if (strstr(filename, ".cdi")) {
|
||||
disc = cdi_create(filename);
|
||||
disc = cdi_create(filename, verbose);
|
||||
} else if (strstr(filename, ".chd")) {
|
||||
disc = chd_create(filename);
|
||||
disc = chd_create(filename, verbose);
|
||||
} else if (strstr(filename, ".gdi")) {
|
||||
disc = gdi_create(filename);
|
||||
disc = gdi_create(filename, verbose);
|
||||
}
|
||||
|
||||
if (!disc) {
|
||||
|
@ -316,7 +316,9 @@ struct disc *disc_create(const char *filename) {
|
|||
snprintf(disc->uid, sizeof(disc->uid), "%s %s %s %s", disc->prodnme,
|
||||
disc->prodnum, disc->prodver, disc->discnum);
|
||||
|
||||
LOG_INFO("disc_create id=%s", disc->uid);
|
||||
if (verbose) {
|
||||
LOG_INFO("disc_create id=%s", disc->uid);
|
||||
}
|
||||
|
||||
return disc;
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ struct disc {
|
|||
void (*read_sector)(struct disc *, struct track *, int, void *);
|
||||
};
|
||||
|
||||
struct disc *disc_create(const char *filename);
|
||||
struct disc *disc_create(const char *filename, int verbose);
|
||||
void disc_destroy(struct disc *disc);
|
||||
|
||||
int disc_get_format(struct disc *disc);
|
||||
|
|
|
@ -93,7 +93,7 @@ static void gdi_destroy(struct disc *disc) {
|
|||
}
|
||||
}
|
||||
|
||||
static int gdi_parse(struct disc *disc, const char *filename) {
|
||||
static int gdi_parse(struct disc *disc, const char *filename, int verbose) {
|
||||
struct gdi *gdi = (struct gdi *)disc;
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
|
@ -162,8 +162,10 @@ static int gdi_parse(struct disc *disc, const char *filename) {
|
|||
snprintf(track->filename, sizeof(track->filename), "%s" PATH_SEPARATOR "%s",
|
||||
dirname, filename);
|
||||
|
||||
LOG_INFO("gdi_parse track=%d filename='%s' fad=%d secsz=%d", track->num,
|
||||
track->filename, track->fad, track->sector_size);
|
||||
if (verbose) {
|
||||
LOG_INFO("gdi_parse track=%d filename='%s' fad=%d secsz=%d", track->num,
|
||||
track->filename, track->fad, track->sector_size);
|
||||
}
|
||||
}
|
||||
|
||||
/* gdroms contains two sessions, one for the single density area (tracks 0-1)
|
||||
|
@ -191,7 +193,7 @@ static int gdi_parse(struct disc *disc, const char *filename) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
struct disc *gdi_create(const char *filename) {
|
||||
struct disc *gdi_create(const char *filename, int verbose) {
|
||||
struct gdi *gdi = calloc(1, sizeof(struct gdi));
|
||||
|
||||
gdi->destroy = &gdi_destroy;
|
||||
|
@ -205,7 +207,7 @@ struct disc *gdi_create(const char *filename) {
|
|||
|
||||
struct disc *disc = (struct disc *)gdi;
|
||||
|
||||
if (!gdi_parse(disc, filename)) {
|
||||
if (!gdi_parse(disc, filename, verbose)) {
|
||||
gdi_destroy(disc);
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
|
||||
struct disc;
|
||||
|
||||
struct disc *gdi_create(const char *filename);
|
||||
struct disc *gdi_create(const char *filename, int verbose);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -329,14 +329,17 @@ static void as_map(struct memory *mem, struct address_space *space,
|
|||
|
||||
#ifdef HAVE_FASTMEM
|
||||
uint8_t *target = space->base + begin;
|
||||
void *res = NULL;
|
||||
|
||||
if (offset >= 0) {
|
||||
/* map physical memory into the address space */
|
||||
map_shared_memory(mem->shmem, offset, target, size, ACC_READWRITE);
|
||||
res = map_shared_memory(mem->shmem, offset, target, size, ACC_READWRITE);
|
||||
} else {
|
||||
/* disable access to mmio areas */
|
||||
map_shared_memory(mem->shmem, 0x0, target, size, ACC_NONE);
|
||||
res = map_shared_memory(mem->shmem, 0x0, target, size, ACC_NONE);
|
||||
}
|
||||
|
||||
CHECK_NE(res, SHMEM_MAP_FAILED);
|
||||
#else
|
||||
(void)(offset);
|
||||
#endif
|
||||
|
@ -443,15 +446,23 @@ int sh4_init(struct memory *mem) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* note, p0-p3 map to the entire external address space, while p4 only maps to
|
||||
the external regions in between the gaps in its own internal regions. these
|
||||
gaps map to areas 1-3 (0xe4000000-0xefffffff) and 6-7 (0xf8000000-
|
||||
0xffffffff) */
|
||||
|
||||
/* area 0 */
|
||||
sh4_map(mem, SH4_AREA0_BEGIN, SH4_AREA0_END, P0 | P1 | P2 | P3, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_area0_read, (mmio_write_cb)&sh4_area0_write, NULL,
|
||||
NULL);
|
||||
sh4_map(mem, SH4_AREA0_BEGIN, SH4_AICA_MEM_BEGIN - 1, P0 | P1 | P2 | P3,
|
||||
MAP_MMIO, (mmio_read_cb)&sh4_area0_read,
|
||||
(mmio_write_cb)&sh4_area0_write, NULL, NULL);
|
||||
sh4_map(mem, SH4_AICA_MEM_BEGIN, SH4_AICA_MEM_END, P0 | P1 | P2 | P3,
|
||||
MAP_ARAM, NULL, NULL, NULL, NULL);
|
||||
sh4_map(mem, SH4_AICA_MEM_END + 1, SH4_AREA0_END, P0 | P1 | P2 | P3, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_area0_read, (mmio_write_cb)&sh4_area0_write, NULL,
|
||||
NULL);
|
||||
|
||||
/* area 1 */
|
||||
sh4_map(mem, SH4_AREA1_BEGIN, SH4_AREA1_END, P0 | P1 | P2 | P3, MAP_MMIO,
|
||||
sh4_map(mem, SH4_AREA1_BEGIN, SH4_AREA1_END, P0 | P1 | P2 | P3 | P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_area1_read, (mmio_write_cb)&sh4_area1_write, NULL,
|
||||
NULL);
|
||||
#if 0
|
||||
|
@ -466,20 +477,19 @@ int sh4_init(struct memory *mem) {
|
|||
/* area 2 */
|
||||
|
||||
/* area 3 */
|
||||
sh4_map(mem, SH4_AREA3_RAM0_BEGIN, SH4_AREA3_RAM0_END, P0 | P1 | P2 | P3,
|
||||
sh4_map(mem, SH4_AREA3_RAM0_BEGIN, SH4_AREA3_RAM0_END, P0 | P1 | P2 | P3 | P4,
|
||||
MAP_RAM, NULL, NULL, NULL, NULL);
|
||||
sh4_map(mem, SH4_AREA3_RAM1_BEGIN, SH4_AREA3_RAM1_END, P0 | P1 | P2 | P3,
|
||||
sh4_map(mem, SH4_AREA3_RAM1_BEGIN, SH4_AREA3_RAM1_END, P0 | P1 | P2 | P3 | P4,
|
||||
MAP_RAM, NULL, NULL, NULL, NULL);
|
||||
sh4_map(mem, SH4_AREA3_RAM2_BEGIN, SH4_AREA3_RAM2_END, P0 | P1 | P2 | P3,
|
||||
sh4_map(mem, SH4_AREA3_RAM2_BEGIN, SH4_AREA3_RAM2_END, P0 | P1 | P2 | P3 | P4,
|
||||
MAP_RAM, NULL, NULL, NULL, NULL);
|
||||
sh4_map(mem, SH4_AREA3_RAM3_BEGIN, SH4_AREA3_RAM3_END, P0 | P1 | P2 | P3,
|
||||
sh4_map(mem, SH4_AREA3_RAM3_BEGIN, SH4_AREA3_RAM3_END, P0 | P1 | P2 | P3 | P4,
|
||||
MAP_RAM, NULL, NULL, NULL, NULL);
|
||||
|
||||
/* area 4 */
|
||||
/* note, this region is only accessed through sq / dma transfers, so only a
|
||||
/* area 4. this region is only written through sq / dma transfers, so only a
|
||||
write_string handler is added */
|
||||
sh4_map(mem, SH4_AREA4_BEGIN, SH4_AREA4_END, P0 | P1 | P2 | P3, MAP_MMIO,
|
||||
(mmio_read_cb)&mem_unhandled_read, NULL, NULL,
|
||||
(mmio_read_cb)&sh4_area4_read, NULL, NULL,
|
||||
(mmio_write_string_cb)&sh4_area4_write);
|
||||
|
||||
/* area 5 */
|
||||
|
@ -487,12 +497,22 @@ int sh4_init(struct memory *mem) {
|
|||
/* area 6 */
|
||||
|
||||
/* area 7 */
|
||||
sh4_map(mem, SH4_AREA7_BEGIN, SH4_AREA7_END, P0 | P1 | P2 | P3, MAP_MMIO,
|
||||
sh4_map(mem, SH4_AREA7_BEGIN, SH4_AREA7_END, P0 | P1 | P2 | P3 | P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_area7_read, (mmio_write_cb)&sh4_area7_write, NULL,
|
||||
NULL);
|
||||
|
||||
/* p4 */
|
||||
sh4_map(mem, SH4_P4_BEGIN, SH4_P4_END, P4, MAP_MMIO,
|
||||
/* p4. the unassigned regions have already been mapped to the external address
|
||||
space. instead of mapping the entire p4 area, selectively map each internal
|
||||
region to avoid overwriting the existing mappings */
|
||||
sh4_map(mem, SH4_SQ_BEGIN, SH4_SQ_END, P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_p4_read, (mmio_write_cb)&sh4_p4_write, NULL, NULL);
|
||||
sh4_map(mem, SH4_ICACHE_BEGIN, SH4_ICACHE_END, P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_p4_read, (mmio_write_cb)&sh4_p4_write, NULL, NULL);
|
||||
sh4_map(mem, SH4_ITLB_BEGIN, SH4_ITLB_END, P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_p4_read, (mmio_write_cb)&sh4_p4_write, NULL, NULL);
|
||||
sh4_map(mem, SH4_OCACHE_BEGIN, SH4_OCACHE_END, P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_p4_read, (mmio_write_cb)&sh4_p4_write, NULL, NULL);
|
||||
sh4_map(mem, SH4_UTLB_BEGIN, SH4_UTLB_END, P4, MAP_MMIO,
|
||||
(mmio_read_cb)&sh4_p4_read, (mmio_write_cb)&sh4_p4_write, NULL, NULL);
|
||||
|
||||
return 1;
|
||||
|
@ -512,8 +532,11 @@ uint8_t *mem_ram(struct memory *mem, uint32_t offset) {
|
|||
|
||||
int mem_init(struct memory *mem) {
|
||||
#ifdef HAVE_FASTMEM
|
||||
/* create the shared memory object to back the physical memory */
|
||||
mem->shmem = create_shared_memory("/redream", PHYSICAL_SIZE, ACC_READWRITE);
|
||||
/* create the shared memory object to back the physical memory. note, because
|
||||
mmio regions also map this shared memory object when disabling permissions,
|
||||
the object has to at least be the size of an entire mmio region */
|
||||
size_t shmem_size = MAX(PHYSICAL_SIZE, SH4_AREA_SIZE);
|
||||
mem->shmem = create_shared_memory("/redream", shmem_size, ACC_READWRITE);
|
||||
|
||||
if (mem->shmem == SHMEM_INVALID) {
|
||||
LOG_WARNING("mem_init failed to create shared memory object");
|
||||
|
|
|
@ -266,11 +266,11 @@ static void pvr_reconfigure_spg(struct pvr *pvr) {
|
|||
pvr->line_clock *= 2;
|
||||
}
|
||||
|
||||
const char *mode = "VGA";
|
||||
const char *mode = "vga";
|
||||
if (pvr->SPG_CONTROL->NTSC == 1) {
|
||||
mode = "NTSC";
|
||||
mode = "ntsc";
|
||||
} else if (pvr->SPG_CONTROL->PAL == 1) {
|
||||
mode = "PAL";
|
||||
mode = "pal";
|
||||
}
|
||||
|
||||
LOG_INFO(
|
||||
|
|
|
@ -365,33 +365,73 @@ static void tr_parse_bg(struct tr *tr, const struct ta_context *ctx,
|
|||
/* translate the first 3 vertices */
|
||||
struct ta_vertex *va = tr_reserve_vert(tr, rc);
|
||||
struct ta_vertex *vb = tr_reserve_vert(tr, rc);
|
||||
struct ta_vertex *vc = tr_reserve_vert(tr, rc);
|
||||
struct ta_vertex *vd = tr_reserve_vert(tr, rc);
|
||||
struct ta_vertex *vc = tr_reserve_vert(tr, rc);
|
||||
|
||||
int offset = 0;
|
||||
offset = tr_parse_bg_vert(ctx, rc, offset, va);
|
||||
offset = tr_parse_bg_vert(ctx, rc, offset, vb);
|
||||
offset = tr_parse_bg_vert(ctx, rc, offset, vc);
|
||||
|
||||
/* not exactly sure how ISP_BACKGND_D is supposed to be honored. would be nice
|
||||
to find a game that actually uses the texture parameter to see how the uv
|
||||
coordinates look */
|
||||
/*va->xyz[2] = ctx->bg_depth;
|
||||
/* force z to background clip depth, not sure the purpose of the supplied z */
|
||||
va->xyz[2] = ctx->bg_depth;
|
||||
vb->xyz[2] = ctx->bg_depth;
|
||||
vc->xyz[2] = ctx->bg_depth;*/
|
||||
vc->xyz[2] = ctx->bg_depth;
|
||||
|
||||
/*
|
||||
* these vertices don't seem to come in a particular order. i've seen:
|
||||
* b---c a---b a---b a
|
||||
* | / \ | | / |\
|
||||
* | / and \ | and | / and | \
|
||||
* |/ \| |/ | \
|
||||
* a c c c---b
|
||||
*
|
||||
* in order to simplify calculating d, figure out the corner vertex based on
|
||||
* the distance between its adjacent vertices, and rotate the vertices such
|
||||
* that b is always the corner vertex. this feels pretty brute force, there
|
||||
* must be a more elegant solution
|
||||
*/
|
||||
float d1[3], d2[3];
|
||||
float scorea, scoreb, scorec;
|
||||
|
||||
vec3_sub(d1, vb->xyz, va->xyz);
|
||||
vec3_sub(d2, vc->xyz, va->xyz);
|
||||
scorea = vec3_dot(d1, d1) + vec3_dot(d2, d2);
|
||||
|
||||
vec3_sub(d1, va->xyz, vb->xyz);
|
||||
vec3_sub(d2, vc->xyz, vb->xyz);
|
||||
scoreb = vec3_dot(d1, d1) + vec3_dot(d2, d2);
|
||||
|
||||
vec3_sub(d1, va->xyz, vc->xyz);
|
||||
vec3_sub(d2, vb->xyz, vc->xyz);
|
||||
scorec = vec3_dot(d1, d1) + vec3_dot(d2, d2);
|
||||
|
||||
if (scorea < scoreb && scorea < scorec) {
|
||||
/* rotate counterclockwise */
|
||||
struct ta_vertex tmp = *va;
|
||||
*va = *vc;
|
||||
*vc = *vb;
|
||||
*vb = tmp;
|
||||
} else if (scorec < scorea && scorec < scoreb) {
|
||||
/* rotate clockwise */
|
||||
struct ta_vertex tmp = *vc;
|
||||
*vc = *va;
|
||||
*va = *vb;
|
||||
*vb = tmp;
|
||||
}
|
||||
|
||||
/* 4th vertex isn't supplied, fill it out automatically */
|
||||
float xyz_ab[3], xyz_ac[3];
|
||||
vec3_sub(xyz_ab, vb->xyz, va->xyz);
|
||||
vec3_sub(xyz_ac, vc->xyz, va->xyz);
|
||||
vec3_add(vd->xyz, vb->xyz, xyz_ab);
|
||||
vec3_add(vd->xyz, vd->xyz, xyz_ac);
|
||||
float xyz_ba[3], xyz_bc[3];
|
||||
vec3_sub(xyz_ba, va->xyz, vb->xyz);
|
||||
vec3_sub(xyz_bc, vc->xyz, vb->xyz);
|
||||
vec3_add(vd->xyz, vb->xyz, xyz_ba);
|
||||
vec3_add(vd->xyz, vd->xyz, xyz_bc);
|
||||
|
||||
float uv_ab[2], uv_ac[2];
|
||||
vec2_sub(uv_ab, vb->uv, va->uv);
|
||||
vec2_sub(uv_ac, vc->uv, va->uv);
|
||||
vec2_add(vd->uv, vb->uv, uv_ab);
|
||||
vec2_add(vd->uv, vd->uv, uv_ac);
|
||||
float uv_ba[2], uv_bc[2];
|
||||
vec2_sub(uv_ba, va->uv, vb->uv);
|
||||
vec2_sub(uv_bc, vc->uv, vb->uv);
|
||||
vec2_add(vd->uv, vb->uv, uv_ba);
|
||||
vec2_add(vd->uv, vd->uv, uv_bc);
|
||||
|
||||
/* TODO interpolate this properly when a game is found to test with */
|
||||
vd->color = va->color;
|
||||
|
|
|
@ -48,9 +48,11 @@ static int boot_validate(struct boot *boot) {
|
|||
static int boot_load_rom(struct boot *boot) {
|
||||
const char *filename = boot_bin_path();
|
||||
|
||||
LOG_INFO("boot_load_rom path=%s", filename);
|
||||
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
if (!fp) {
|
||||
LOG_WARNING("failed to load '%s'", filename);
|
||||
LOG_WARNING("boot_load_rom failed to open");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -59,7 +61,7 @@ static int boot_load_rom(struct boot *boot) {
|
|||
fseek(fp, 0, SEEK_SET);
|
||||
|
||||
if (size != (int)sizeof(boot->rom)) {
|
||||
LOG_WARNING("boot rom size mismatch, is %d, expected %d", size,
|
||||
LOG_WARNING("boot_load_rom size mismatch size=%d expected=%d", size,
|
||||
sizeof(boot->rom));
|
||||
fclose(fp);
|
||||
return 0;
|
||||
|
@ -70,12 +72,10 @@ static int boot_load_rom(struct boot *boot) {
|
|||
fclose(fp);
|
||||
|
||||
if (!boot_validate(boot)) {
|
||||
LOG_WARNING("failed to validate boot rom");
|
||||
LOG_WARNING("boot_load_rom failed to validate");
|
||||
return 0;
|
||||
}
|
||||
|
||||
LOG_INFO("boot_load_rom loaded '%s'", filename);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,9 @@ struct sh4 {
|
|||
int tmu_stats;
|
||||
struct list breakpoints;
|
||||
|
||||
/* ccn */
|
||||
uint32_t sq[2][8];
|
||||
|
||||
/* intc */
|
||||
enum sh4_interrupt sorted_interrupts[SH4_NUM_INTERRUPTS];
|
||||
uint64_t sort_id[SH4_NUM_INTERRUPTS];
|
||||
|
|
|
@ -57,7 +57,7 @@ void sh4_ccn_pref(struct sh4 *sh4, uint32_t addr) {
|
|||
dst |= addr & 0x3ffffe0;
|
||||
}
|
||||
|
||||
sh4_memcpy_to_guest(mem, dst, sh4->ctx.sq[sqi], 32);
|
||||
sh4_memcpy_to_guest(mem, dst, sh4->sq[sqi], 32);
|
||||
}
|
||||
|
||||
uint32_t sh4_ccn_cache_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
||||
|
@ -86,15 +86,17 @@ void sh4_ccn_cache_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
|||
|
||||
uint32_t sh4_ccn_sq_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
||||
uint32_t sqi = (addr & 0x20) >> 5;
|
||||
unsigned idx = (addr & 0x1c) >> 2;
|
||||
return sh4->ctx.sq[sqi][idx];
|
||||
uint32_t idx = (addr & 0x1c) >> 2;
|
||||
CHECK_EQ(mask, 0xffffffff);
|
||||
return sh4->sq[sqi][idx];
|
||||
}
|
||||
|
||||
void sh4_ccn_sq_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
||||
uint32_t mask) {
|
||||
uint32_t sqi = (addr & 0x20) >> 5;
|
||||
uint32_t idx = (addr & 0x1c) >> 2;
|
||||
sh4->ctx.sq[sqi][idx] = data;
|
||||
CHECK_EQ(mask, 0xffffffff);
|
||||
sh4->sq[sqi][idx] = data;
|
||||
}
|
||||
|
||||
uint32_t sh4_ccn_icache_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "guest/aica/aica.h"
|
||||
#include "guest/holly/holly.h"
|
||||
#include "guest/memory.h"
|
||||
#include "guest/pvr/pvr.h"
|
||||
#include "guest/pvr/ta.h"
|
||||
#include "guest/rom/boot.h"
|
||||
|
@ -53,12 +54,11 @@ void sh4_p4_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
|||
sh4_ccn_ocache_write(sh4, addr - SH4_OCACHE_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_UTLB_BEGIN && addr <= SH4_UTLB_END) {
|
||||
sh4_mmu_utlb_write(sh4, addr - SH4_UTLB_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_REG2_BEGIN && addr <= SH4_REG2_END) {
|
||||
sh4_reg_write(sh4, addr - SH4_REG2_BEGIN, data, mask);
|
||||
} else {
|
||||
LOG_FATAL("sh4_p4_write unexpected addr 0x%08x", addr);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sh4_p4_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
||||
if (addr >= SH4_SQ_BEGIN && addr <= SH4_SQ_END) {
|
||||
return sh4_ccn_sq_read(sh4, addr - SH4_SQ_BEGIN, mask);
|
||||
|
@ -70,8 +70,6 @@ uint32_t sh4_p4_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
|||
return sh4_ccn_ocache_read(sh4, addr - SH4_OCACHE_BEGIN, mask);
|
||||
} else if (addr >= SH4_UTLB_BEGIN && addr <= SH4_UTLB_END) {
|
||||
return sh4_mmu_utlb_read(sh4, addr - SH4_UTLB_BEGIN, mask);
|
||||
} else if (addr >= SH4_REG2_BEGIN && addr <= SH4_REG2_END) {
|
||||
return sh4_reg_read(sh4, addr - SH4_REG2_BEGIN, mask);
|
||||
} else {
|
||||
LOG_FATAL("sh4_p4_read unexpected addr 0x%08x", addr);
|
||||
}
|
||||
|
@ -117,7 +115,7 @@ void sh4_area4_write(struct sh4 *sh4, uint32_t addr, const uint8_t *ptr,
|
|||
|
||||
addr &= SH4_ADDR_MASK;
|
||||
|
||||
/* create the two area4 mirrors */
|
||||
/* create the mirror */
|
||||
addr &= SH4_AREA4_ADDR_MASK;
|
||||
|
||||
if (addr >= SH4_TA_POLY_BEGIN && addr <= SH4_TA_POLY_END) {
|
||||
|
@ -127,17 +125,27 @@ void sh4_area4_write(struct sh4 *sh4, uint32_t addr, const uint8_t *ptr,
|
|||
} else if (addr >= SH4_TA_TEXTURE_BEGIN && addr <= SH4_TA_TEXTURE_END) {
|
||||
ta_texture_write(dc->ta, addr, ptr, size);
|
||||
} else {
|
||||
LOG_FATAL("sh4_area4_write unexpected addr 0x%08x", addr);
|
||||
/* nop */
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sh4_area4_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
||||
addr &= SH4_ADDR_MASK;
|
||||
|
||||
/* create the mirror */
|
||||
addr &= SH4_AREA4_ADDR_MASK;
|
||||
|
||||
/* area 4 is read-only, but will return the physical address when accessed */
|
||||
return addr;
|
||||
}
|
||||
|
||||
void sh4_area1_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
||||
uint32_t mask) {
|
||||
struct dreamcast *dc = sh4->dc;
|
||||
|
||||
addr &= SH4_ADDR_MASK;
|
||||
|
||||
/* create the two area1 mirrors */
|
||||
/* create the mirror */
|
||||
addr &= SH4_AREA1_ADDR_MASK;
|
||||
|
||||
if (addr >= SH4_PVR_VRAM64_BEGIN && addr <= SH4_PVR_VRAM64_END) {
|
||||
|
@ -154,7 +162,7 @@ uint32_t sh4_area1_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
|||
|
||||
addr &= SH4_ADDR_MASK;
|
||||
|
||||
/* create the two area1 mirrors */
|
||||
/* create the mirror */
|
||||
addr &= SH4_AREA1_ADDR_MASK;
|
||||
|
||||
if (addr >= SH4_PVR_VRAM64_BEGIN && addr <= SH4_PVR_VRAM64_END) {
|
||||
|
@ -173,28 +181,31 @@ void sh4_area0_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
|||
/* mask off upper bits creating p0-p4 mirrors */
|
||||
addr &= SH4_ADDR_MASK;
|
||||
|
||||
/* flash rom is not accessible in the area0 mirror */
|
||||
/* flash rom is not accessible in the area 0 mirror */
|
||||
if (addr >= SH4_FLASH_ROM_BEGIN && addr <= SH4_FLASH_ROM_END) {
|
||||
flash_rom_write(dc->flash, addr - SH4_FLASH_ROM_BEGIN, data, mask);
|
||||
} else {
|
||||
/* create the two area0 mirrors */
|
||||
addr &= SH4_AREA0_ADDR_MASK;
|
||||
return;
|
||||
}
|
||||
|
||||
if (addr >= SH4_HOLLY_REG_BEGIN && addr <= SH4_HOLLY_REG_END) {
|
||||
holly_reg_write(dc->holly, addr - SH4_HOLLY_REG_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_PVR_REG_BEGIN && addr <= SH4_PVR_REG_END) {
|
||||
pvr_reg_write(dc->pvr, addr - SH4_PVR_REG_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_MODEM_BEGIN && addr <= SH4_MODEM_END) {
|
||||
/* nop */
|
||||
} else if (addr >= SH4_AICA_REG_BEGIN && addr <= SH4_AICA_REG_END) {
|
||||
aica_reg_write(dc->aica, addr - SH4_AICA_REG_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_AICA_MEM_BEGIN && addr <= SH4_AICA_MEM_END) {
|
||||
aica_mem_write(dc->aica, addr - SH4_AICA_MEM_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_HOLLY_EXT_BEGIN && addr <= SH4_HOLLY_EXT_END) {
|
||||
/* nop */
|
||||
} else {
|
||||
LOG_FATAL("sh4_area0_write unexpected addr 0x%08x", addr);
|
||||
}
|
||||
/* create the mirror */
|
||||
addr &= SH4_AREA0_ADDR_MASK;
|
||||
|
||||
if (/*addr >= SH4_BOOT_ROM_BEGIN*/ addr <= SH4_BOOT_ROM_END) {
|
||||
/* read-only */
|
||||
} else if (addr >= SH4_HOLLY_REG_BEGIN && addr <= SH4_HOLLY_REG_END) {
|
||||
holly_reg_write(dc->holly, addr - SH4_HOLLY_REG_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_PVR_REG_BEGIN && addr <= SH4_PVR_REG_END) {
|
||||
pvr_reg_write(dc->pvr, addr - SH4_PVR_REG_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_MODEM_BEGIN && addr <= SH4_MODEM_END) {
|
||||
/* nop */
|
||||
} else if (addr >= SH4_AICA_REG_BEGIN && addr <= SH4_AICA_REG_END) {
|
||||
aica_reg_write(dc->aica, addr - SH4_AICA_REG_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_AICA_MEM_BEGIN && addr <= SH4_AICA_MEM_END) {
|
||||
aica_mem_write(dc->aica, addr - SH4_AICA_MEM_BEGIN, data, mask);
|
||||
} else if (addr >= SH4_HOLLY_EXT_BEGIN && addr <= SH4_HOLLY_EXT_END) {
|
||||
/* nop */
|
||||
} else {
|
||||
LOG_FATAL("sh4_area0_write unexpected addr 0x%08x", addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,17 +215,21 @@ uint32_t sh4_area0_read(struct sh4 *sh4, uint32_t addr, uint32_t mask) {
|
|||
/* mask off upper bits creating p0-p4 mirrors */
|
||||
addr &= SH4_ADDR_MASK;
|
||||
|
||||
/* boot / flash rom are not accessible in the area0 mirror */
|
||||
/* boot / flash rom are not accessible in the area 0 mirror */
|
||||
if (/*addr >= SH4_BOOT_ROM_BEGIN &&*/ addr <= SH4_BOOT_ROM_END) {
|
||||
return boot_rom_read(dc->boot, addr - SH4_BOOT_ROM_BEGIN, mask);
|
||||
} else if (addr >= SH4_FLASH_ROM_BEGIN && addr <= SH4_FLASH_ROM_END) {
|
||||
return flash_rom_read(dc->flash, addr - SH4_FLASH_ROM_BEGIN, mask);
|
||||
}
|
||||
|
||||
/* create the two area0 mirrors */
|
||||
/* create the mirror */
|
||||
addr &= SH4_AREA0_ADDR_MASK;
|
||||
|
||||
if (addr >= SH4_HOLLY_REG_BEGIN && addr <= SH4_HOLLY_REG_END) {
|
||||
if (/*addr >= SH4_BOOT_ROM_BEGIN*/ addr <= SH4_BOOT_ROM_END) {
|
||||
return 0xffffffff;
|
||||
} else if (addr >= SH4_FLASH_ROM_BEGIN && addr <= SH4_FLASH_ROM_END) {
|
||||
return 0xffffffff;
|
||||
} else if (addr >= SH4_HOLLY_REG_BEGIN && addr <= SH4_HOLLY_REG_END) {
|
||||
return holly_reg_read(dc->holly, addr - SH4_HOLLY_REG_BEGIN, mask);
|
||||
} else if (addr >= SH4_PVR_REG_BEGIN && addr <= SH4_PVR_REG_END) {
|
||||
return pvr_reg_read(dc->pvr, addr - SH4_PVR_REG_BEGIN, mask);
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
#define SH4_MEM_H
|
||||
|
||||
/* clang-format off */
|
||||
#define SH4_ADDR_MASK 0x1fffffff
|
||||
#define SH4_AREA_SIZE 0x20000000
|
||||
#define SH4_ADDR_MASK (SH4_AREA_SIZE-1)
|
||||
|
||||
/* area 0 */
|
||||
#define SH4_AREA0_BEGIN 0x00000000
|
||||
|
@ -116,21 +117,24 @@
|
|||
#define SH4_OCACHE_END 0xf5ffffff
|
||||
#define SH4_UTLB_BEGIN 0xf6000000
|
||||
#define SH4_UTLB_END 0xf7ffffff
|
||||
#define SH4_REG2_BEGIN 0xfc000000
|
||||
#define SH4_REG2_END 0xffffffff
|
||||
/* clang-format on */
|
||||
|
||||
uint32_t sh4_area0_read(struct sh4 *sh4, uint32_t addr, uint32_t mask);
|
||||
void sh4_area0_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
||||
uint32_t mask);
|
||||
|
||||
uint32_t sh4_area1_read(struct sh4 *sh4, uint32_t addr, uint32_t mask);
|
||||
void sh4_area1_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
||||
uint32_t mask);
|
||||
|
||||
uint32_t sh4_area4_read(struct sh4 *sh4, uint32_t addr, uint32_t mask);
|
||||
void sh4_area4_write(struct sh4 *sh4, uint32_t addr, const uint8_t *ptr,
|
||||
int size);
|
||||
|
||||
uint32_t sh4_area7_read(struct sh4 *sh4, uint32_t addr, uint32_t mask);
|
||||
void sh4_area7_write(struct sh4 *sh4, uint32_t addr, uint32_t data,
|
||||
uint32_t mask);
|
||||
|
||||
uint32_t sh4_p4_read(struct sh4 *sh4, uint32_t addr, uint32_t mask);
|
||||
void sh4_p4_write(struct sh4 *sh4, uint32_t addr, uint32_t data, uint32_t mask);
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "core/profiler.h"
|
||||
#include "core/ringbuf.h"
|
||||
#include "core/time.h"
|
||||
#include "core/version.h"
|
||||
#include "emulator.h"
|
||||
#include "guest/aica/aica.h"
|
||||
#include "host/host.h"
|
||||
|
@ -287,9 +288,17 @@ static SDL_GLContext video_create_context(struct host *host) {
|
|||
}
|
||||
|
||||
static void video_set_fullscreen(struct host *host, int fullscreen) {
|
||||
uint32_t flags = fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
|
||||
int res = SDL_SetWindowFullscreen(host->win, flags);
|
||||
CHECK_EQ(res, 0);
|
||||
if (fullscreen) {
|
||||
int res = SDL_SetWindowFullscreen(host->win, SDL_WINDOW_FULLSCREEN_DESKTOP);
|
||||
CHECK(res >= 0);
|
||||
res = SDL_ShowCursor(SDL_DISABLE);
|
||||
CHECK(res >= 0);
|
||||
} else {
|
||||
int res = SDL_SetWindowFullscreen(host->win, 0);
|
||||
CHECK(res >= 0);
|
||||
res = SDL_ShowCursor(SDL_ENABLE);
|
||||
CHECK(res >= 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void video_shutdown(struct host *host) {
|
||||
|
@ -687,7 +696,7 @@ static void host_debug_menu(struct host *host) {
|
|||
int enabled = options[i].enabled;
|
||||
|
||||
if (igMenuItem(desc, NULL, enabled, 1)) {
|
||||
int len = strlen(OPTION_sync);
|
||||
int len = (int)strlen(OPTION_sync);
|
||||
|
||||
if (enabled) {
|
||||
for (int i = 0; i < len; i++) {
|
||||
|
@ -967,16 +976,16 @@ static int host_init(struct host *host) {
|
|||
CHECK_GE(res, 0, "host_create sdl initialization failed: %s", SDL_GetError());
|
||||
|
||||
uint32_t win_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
|
||||
if (OPTION_fullscreen) {
|
||||
win_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
host->win = SDL_CreateWindow("redream", SDL_WINDOWPOS_UNDEFINED,
|
||||
SDL_WINDOWPOS_UNDEFINED, VIDEO_DEFAULT_WIDTH,
|
||||
VIDEO_DEFAULT_HEIGHT, win_flags);
|
||||
CHECK_NOTNULL(host->win, "host_create window creation failed: %s",
|
||||
SDL_GetError());
|
||||
|
||||
if (OPTION_fullscreen) {
|
||||
video_set_fullscreen(host, 1);
|
||||
}
|
||||
|
||||
if (!audio_init(host)) {
|
||||
return 0;
|
||||
}
|
||||
|
@ -1008,6 +1017,8 @@ struct host *host_create() {
|
|||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
LOG_INFO("redream " GIT_VERSION);
|
||||
|
||||
#if PLATFORM_ANDROID
|
||||
const char *appdir = SDL_AndroidGetExternalStoragePath();
|
||||
fs_set_appdir(appdir);
|
||||
|
|
|
@ -247,8 +247,6 @@ static void imgui_update_font_tex(struct imgui *imgui) {
|
|||
return;
|
||||
}
|
||||
|
||||
LOG_INFO("imgui_update_font_tex w=%d h=%d", width, height);
|
||||
|
||||
font_tex = r_create_texture(imgui->r, PXL_RGBA, FILTER_BILINEAR, WRAP_REPEAT,
|
||||
WRAP_REPEAT, 0, width, height, pixels);
|
||||
io.Fonts->TexID = (void *)(intptr_t)font_tex;
|
||||
|
|
|
@ -15,34 +15,6 @@ extern "C" {
|
|||
* x64 register layout
|
||||
*/
|
||||
|
||||
/* %rax %eax %ax %al <-- both: temporary
|
||||
%rcx %ecx %cx %cl <-- both: argument
|
||||
%rdx %edx %dx %dl <-- both: argument
|
||||
%rbx %ebx %bx %bl <-- both: available (callee saved)
|
||||
%rsp %esp %sp %spl <-- both: reserved
|
||||
%rbp %ebp %bp %bpl <-- both: available (callee saved)
|
||||
%rsi %esi %si %sil <-- msvc: available (callee saved), amd64: argument
|
||||
%rdi %edi %di %dil <-- msvc: available (callee saved), amd64: argument
|
||||
%r8 %r8d %r8w %r8b <-- both: argument
|
||||
%r9 %r9d %r9w %r9b <-- both: argument
|
||||
%r10 %r10d %r10w %r10b <-- both: available (not callee saved)
|
||||
%r11 %r11d %r11w %r11b <-- both: available (not callee saved)
|
||||
%r12 %r12d %r12w %r12b <-- both: available (callee saved)
|
||||
%r13 %r13d %r13w %r13b <-- both: available (callee saved)
|
||||
%r14 %r14d %r14w %r14b <-- both: available (callee saved)
|
||||
%r15 %r15d %r15w %r15b <-- both: available (callee saved)
|
||||
|
||||
msvc calling convention uses rcx, rdx, r8, r9 for arguments
|
||||
amd64 calling convention uses rdi, rsi, rdx, rcx, r8, r9 for arguments
|
||||
both use the same xmm registers for floating point arguments
|
||||
our largest function call uses only 3 arguments
|
||||
msvc is left with rax, rsi, rdi, r10 and r11
|
||||
amd64 is left with rax, r8, r9, r10 and r11
|
||||
|
||||
rax is used as a scratch register
|
||||
r10, r11, xmm1 are used for constant not eliminated by const propagation
|
||||
r14, r15 are reserved for the context and memory pointers */
|
||||
|
||||
/* clang-format off */
|
||||
#if PLATFORM_WINDOWS
|
||||
const int x64_arg0_idx = Xbyak::Operand::RCX;
|
||||
|
@ -64,29 +36,64 @@ const Xbyak::Reg64 guestctx(Xbyak::Operand::R14);
|
|||
const Xbyak::Reg64 guestmem(Xbyak::Operand::R15);
|
||||
|
||||
const struct jit_register x64_registers[] = {
|
||||
{"rbx", JIT_REG_I64 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::rbx},
|
||||
{"rbp", JIT_REG_I64 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::rbp},
|
||||
{"rax", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rax},
|
||||
{"rcx", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rcx},
|
||||
{"rdx", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rdx},
|
||||
{"rbx", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rbx},
|
||||
{"rsp", JIT_RESERVED | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rsp},
|
||||
{"rbp", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rbp},
|
||||
#if PLATFORM_WINDOWS
|
||||
{"rsi", JIT_REG_I64 | JIT_CALLER_SAVED, (const void *)&Xbyak::util::rsi},
|
||||
{"rdi", JIT_REG_I64 | JIT_CALLER_SAVED, (const void *)&Xbyak::util::rdi},
|
||||
{"rsi", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rsi},
|
||||
{"rdi", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rdi},
|
||||
{"r8", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r8},
|
||||
{"r9", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r9},
|
||||
#else
|
||||
{"r8", JIT_REG_I64 | JIT_CALLER_SAVED, (const void *)&Xbyak::util::r8},
|
||||
{"r9", JIT_REG_I64 | JIT_CALLER_SAVED, (const void *)&Xbyak::util::r9},
|
||||
{"rsi", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rsi},
|
||||
{"rdi", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::rdi},
|
||||
{"r8", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r8},
|
||||
{"r9", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r9},
|
||||
#endif
|
||||
{"r10", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r10},
|
||||
{"r11", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r11},
|
||||
{"r12", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r12},
|
||||
{"r13", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r13},
|
||||
{"r14", JIT_RESERVED | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r14},
|
||||
{"r15", JIT_RESERVED | JIT_CALLEE_SAVE | JIT_REG_I64, (const void *)&Xbyak::util::r15},
|
||||
#if PLATFORM_WINDOWS
|
||||
{"xmm0", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm0},
|
||||
{"xmm1", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm1},
|
||||
{"xmm2", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm2},
|
||||
{"xmm3", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm3},
|
||||
{"xmm4", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm4},
|
||||
{"xmm5", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm5},
|
||||
{"xmm6", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm6},
|
||||
{"xmm7", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm7},
|
||||
{"xmm8", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm8},
|
||||
{"xmm9", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm9},
|
||||
{"xmm10", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm10},
|
||||
{"xmm11", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm11},
|
||||
{"xmm12", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm12},
|
||||
{"xmm13", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm13},
|
||||
{"xmm14", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm14},
|
||||
{"xmm15", JIT_ALLOCATE | JIT_CALLEE_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm15},
|
||||
#else
|
||||
{"xmm0", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm0},
|
||||
{"xmm1", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm1},
|
||||
{"xmm2", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm2},
|
||||
{"xmm3", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm3},
|
||||
{"xmm4", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm4},
|
||||
{"xmm5", JIT_RESERVED | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm5},
|
||||
{"xmm6", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm6},
|
||||
{"xmm7", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm7},
|
||||
{"xmm8", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm8},
|
||||
{"xmm9", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm9},
|
||||
{"xmm10", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm10},
|
||||
{"xmm11", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm11},
|
||||
{"xmm12", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm12},
|
||||
{"xmm13", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm13},
|
||||
{"xmm14", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm14},
|
||||
{"xmm15", JIT_ALLOCATE | JIT_CALLER_SAVE | JIT_REG_F64 | JIT_REG_V128, (const void *)&Xbyak::util::xmm15},
|
||||
#endif
|
||||
{"r10", JIT_REG_I64 | JIT_CALLER_SAVED, (const void *)&Xbyak::util::r10},
|
||||
{"r11", JIT_REG_I64 | JIT_CALLER_SAVED, (const void *)&Xbyak::util::r11},
|
||||
{"r12", JIT_REG_I64 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::r12},
|
||||
{"r13", JIT_REG_I64 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::r13},
|
||||
{"xmm6", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm6},
|
||||
{"xmm7", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm7},
|
||||
{"xmm8", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm8},
|
||||
{"xmm9", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm9},
|
||||
{"xmm10", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm10},
|
||||
{"xmm11", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm11},
|
||||
{"xmm12", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm12},
|
||||
{"xmm13", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm13},
|
||||
{"xmm14", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm14},
|
||||
{"xmm15", JIT_REG_F64 | JIT_REG_V128 | JIT_CALLEE_SAVED, (const void *)&Xbyak::util::xmm15}
|
||||
};
|
||||
|
||||
const int x64_num_registers = ARRAY_SIZE(x64_registers);
|
||||
|
@ -127,6 +134,60 @@ Xbyak::Xmm x64_backend_xmm(struct x64_backend *backend,
|
|||
return xmm;
|
||||
}
|
||||
|
||||
int x64_backend_push_regs(struct x64_backend *backend, int mask) {
|
||||
int size = 0;
|
||||
|
||||
auto &e = *backend->codegen;
|
||||
|
||||
for (int i = 0; i < x64_num_registers; i++) {
|
||||
const struct jit_register *r = &x64_registers[i];
|
||||
|
||||
if ((r->flags & mask) != mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (r->flags & JIT_REG_I64) {
|
||||
Xbyak::Reg reg = *(const Xbyak::Reg *)r->data;
|
||||
CHECK(reg.isREG());
|
||||
size += 8;
|
||||
e.mov(e.qword[e.rsp - size], reg);
|
||||
} else if (r->flags & (JIT_REG_F64 | JIT_REG_V128)) {
|
||||
Xbyak::Xmm xmm = *(const Xbyak::Xmm *)r->data;
|
||||
CHECK(xmm.isXMM());
|
||||
size += 16;
|
||||
e.movdqu(e.ptr[e.rsp - size], xmm);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void x64_backend_pop_regs(struct x64_backend *backend, int mask) {
|
||||
int size = 0;
|
||||
|
||||
auto &e = *backend->codegen;
|
||||
|
||||
for (int i = 0; i < x64_num_registers; i++) {
|
||||
const struct jit_register *r = &x64_registers[i];
|
||||
|
||||
if ((r->flags & mask) != mask) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((r->flags & JIT_REG_I64)) {
|
||||
Xbyak::Reg reg = *(const Xbyak::Reg *)r->data;
|
||||
CHECK(reg.isREG());
|
||||
size += 8;
|
||||
e.mov(reg, e.qword[e.rsp - size]);
|
||||
} else if (r->flags & (JIT_REG_F64 | JIT_REG_V128)) {
|
||||
Xbyak::Xmm xmm = *(const Xbyak::Xmm *)r->data;
|
||||
CHECK(xmm.isXMM());
|
||||
size += 16;
|
||||
e.movdqu(xmm, e.ptr[e.rsp - size]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void x64_backend_load_mem(struct x64_backend *backend,
|
||||
const struct ir_value *dst,
|
||||
const Xbyak::RegExp &src_exp) {
|
||||
|
@ -306,33 +367,19 @@ static void x64_backend_emit_thunks(struct x64_backend *backend) {
|
|||
|
||||
backend->load_thunk[i] = e.getCurr<void (*)()>();
|
||||
|
||||
/* save caller-saved registers and offset the stack an extra 8 bytes to
|
||||
align it */
|
||||
#if PLATFORM_WINDOWS
|
||||
e.push(e.rsi);
|
||||
e.push(e.rdi);
|
||||
#else
|
||||
e.push(e.r8);
|
||||
e.push(e.r9);
|
||||
#endif
|
||||
e.push(e.r10);
|
||||
e.push(e.r11);
|
||||
e.sub(e.rsp, X64_STACK_SHADOW_SPACE + 8);
|
||||
/* save caller-saved registers that our code uses and ensure stack is
|
||||
16-byte aligned */
|
||||
int save_mask = JIT_ALLOCATE | JIT_CALLER_SAVE;
|
||||
int offset = x64_backend_push_regs(backend, save_mask);
|
||||
offset = ALIGN_UP(offset + X64_STACK_SHADOW_SPACE + 8, 16) - 8;
|
||||
e.sub(e.rsp, offset);
|
||||
|
||||
/* call the mmio handler */
|
||||
e.call(e.rax);
|
||||
|
||||
/* restore caller-saved registers */
|
||||
e.add(e.rsp, X64_STACK_SHADOW_SPACE + 8);
|
||||
e.pop(e.r11);
|
||||
e.pop(e.r10);
|
||||
#if PLATFORM_WINDOWS
|
||||
e.pop(e.rdi);
|
||||
e.pop(e.rsi);
|
||||
#else
|
||||
e.pop(e.r9);
|
||||
e.pop(e.r8);
|
||||
#endif
|
||||
e.add(e.rsp, offset);
|
||||
x64_backend_pop_regs(backend, save_mask);
|
||||
|
||||
/* save mmio handler result */
|
||||
e.mov(dst, e.rax);
|
||||
|
@ -346,34 +393,19 @@ static void x64_backend_emit_thunks(struct x64_backend *backend) {
|
|||
e.align(32);
|
||||
|
||||
backend->store_thunk = e.getCurr<void (*)()>();
|
||||
|
||||
/* save caller-saved registers and offset the stack an extra 8 bytes to
|
||||
align it */
|
||||
#if PLATFORM_WINDOWS
|
||||
e.push(e.rsi);
|
||||
e.push(e.rdi);
|
||||
#else
|
||||
e.push(e.r8);
|
||||
e.push(e.r9);
|
||||
#endif
|
||||
e.push(e.r10);
|
||||
e.push(e.r11);
|
||||
e.sub(e.rsp, X64_STACK_SHADOW_SPACE + 8);
|
||||
/* save caller-saved registers that our code uses and ensure stack is
|
||||
16-byte aligned */
|
||||
int save_mask = JIT_ALLOCATE | JIT_CALLER_SAVE;
|
||||
int offset = x64_backend_push_regs(backend, save_mask);
|
||||
offset = ALIGN_UP(offset + X64_STACK_SHADOW_SPACE + 8, 16) - 8;
|
||||
e.sub(e.rsp, offset);
|
||||
|
||||
/* call the mmio handler */
|
||||
e.call(e.rax);
|
||||
|
||||
/* restore caller-saved registers */
|
||||
e.add(e.rsp, X64_STACK_SHADOW_SPACE + 8);
|
||||
e.pop(e.r11);
|
||||
e.pop(e.r10);
|
||||
#if PLATFORM_WINDOWS
|
||||
e.pop(e.rdi);
|
||||
e.pop(e.rsi);
|
||||
#else
|
||||
e.pop(e.r9);
|
||||
e.pop(e.r8);
|
||||
#endif
|
||||
e.add(e.rsp, offset);
|
||||
x64_backend_pop_regs(backend, save_mask);
|
||||
|
||||
/* return to jit code */
|
||||
e.ret();
|
||||
|
|
|
@ -75,6 +75,7 @@ void x64_dispatch_emit_thunks(struct x64_backend *backend) {
|
|||
struct jit_guest *guest = backend->base.guest;
|
||||
|
||||
auto &e = *backend->codegen;
|
||||
int stack_offset = 0;
|
||||
|
||||
/* emit dispatch thunks */
|
||||
{
|
||||
|
@ -152,18 +153,12 @@ void x64_dispatch_emit_thunks(struct x64_backend *backend) {
|
|||
|
||||
backend->dispatch_enter = e.getCurr<void (*)(int)>();
|
||||
|
||||
/* create stack frame */
|
||||
e.push(e.rbx);
|
||||
e.push(e.rbp);
|
||||
#if PLATFORM_WINDOWS
|
||||
e.push(e.rdi);
|
||||
e.push(e.rsi);
|
||||
#endif
|
||||
e.push(e.r12);
|
||||
e.push(e.r13);
|
||||
e.push(e.r14);
|
||||
e.push(e.r15);
|
||||
e.sub(e.rsp, X64_STACK_SIZE + 8);
|
||||
/* create stack frame and ensure stack is 16-byte aligned. note, the stack
|
||||
is currently unaligned due to the 8-byte return address that was pushed
|
||||
when this thunk was called */
|
||||
stack_offset = x64_backend_push_regs(backend, JIT_CALLEE_SAVE);
|
||||
stack_offset = ALIGN_UP(stack_offset + X64_STACK_SIZE + 8, 16) - 8;
|
||||
e.sub(e.rsp, stack_offset);
|
||||
|
||||
/* assign fixed registers */
|
||||
e.mov(guestctx, (uint64_t)guest->ctx);
|
||||
|
@ -184,17 +179,9 @@ void x64_dispatch_emit_thunks(struct x64_backend *backend) {
|
|||
backend->dispatch_exit = e.getCurr<void *>();
|
||||
|
||||
/* destroy stack frame */
|
||||
e.add(e.rsp, X64_STACK_SIZE + 8);
|
||||
e.pop(e.r15);
|
||||
e.pop(e.r14);
|
||||
e.pop(e.r13);
|
||||
e.pop(e.r12);
|
||||
#if PLATFORM_WINDOWS
|
||||
e.pop(e.rsi);
|
||||
e.pop(e.rdi);
|
||||
#endif
|
||||
e.pop(e.rbp);
|
||||
e.pop(e.rbx);
|
||||
e.add(e.rsp, stack_offset);
|
||||
x64_backend_pop_regs(backend, JIT_CALLEE_SAVE);
|
||||
|
||||
e.ret();
|
||||
}
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ struct x64_backend {
|
|||
/*
|
||||
* backend functionality used by emitters
|
||||
*/
|
||||
#define X64_THUNK_SIZE 1024
|
||||
#define X64_THUNK_SIZE 8192
|
||||
#define X64_STACK_SIZE 1024
|
||||
|
||||
#if PLATFORM_WINDOWS
|
||||
|
@ -79,6 +79,8 @@ Xbyak::Reg x64_backend_reg(struct x64_backend *backend,
|
|||
const struct ir_value *v);
|
||||
Xbyak::Xmm x64_backend_xmm(struct x64_backend *backend,
|
||||
const struct ir_value *v);
|
||||
int x64_backend_push_regs(struct x64_backend *backend, int mask);
|
||||
void x64_backend_pop_regs(struct x64_backend *backend, int mask);
|
||||
void x64_backend_load_mem(struct x64_backend *backend,
|
||||
const struct ir_value *dst,
|
||||
const Xbyak::RegExp &src_exp);
|
||||
|
|
|
@ -114,7 +114,6 @@ struct sh4_context {
|
|||
uint32_t fpul, mach, macl;
|
||||
uint32_t sgr, spc, ssr;
|
||||
uint64_t pending_interrupts;
|
||||
uint32_t sq[2][8];
|
||||
|
||||
/* processor sleep state */
|
||||
uint32_t sleep_mode;
|
||||
|
|
|
@ -27,33 +27,37 @@ struct jit_guest;
|
|||
#endif
|
||||
|
||||
enum {
|
||||
/* allocate to this register */
|
||||
JIT_ALLOCATE = 0x1,
|
||||
/* don't allocate to this register */
|
||||
JIT_RESERVED = 0x2,
|
||||
/* register is callee-saved */
|
||||
JIT_CALLEE_SAVED = 0x1,
|
||||
JIT_CALLEE_SAVE = 0x4,
|
||||
/* register is caller-saved */
|
||||
JIT_CALLER_SAVED = 0x2,
|
||||
JIT_CALLER_SAVE = 0x8,
|
||||
/* result must contain arg0. this signals the register allocator to insert a
|
||||
copy from arg0 to result if it fails to reuse the same register for both.
|
||||
this is required by several operations, namely binary arithmetic ops on
|
||||
x64, which only take two operands */
|
||||
JIT_REUSE_ARG0 = 0x4,
|
||||
JIT_REUSE_ARG0 = 0x10,
|
||||
/* argument is optional */
|
||||
JIT_OPTIONAL = 0x8,
|
||||
JIT_OPTIONAL = 0x20,
|
||||
/* argument can be in a 64-bit or less int register */
|
||||
JIT_REG_I64 = 0x10,
|
||||
JIT_REG_I64 = 0x40,
|
||||
/* argument can be in a 64-bit or less float register */
|
||||
JIT_REG_F64 = 0x20,
|
||||
JIT_REG_F64 = 0x80,
|
||||
/* argument can be in a 128-bit or less vector register */
|
||||
JIT_REG_V128 = 0x40,
|
||||
JIT_REG_V128 = 0x100,
|
||||
/* argument can be a 32-bit or less int immediate */
|
||||
JIT_IMM_I32 = 0x80,
|
||||
JIT_IMM_I32 = 0x200,
|
||||
/* argument can be a 64-bit or less int immediate */
|
||||
JIT_IMM_I64 = 0x100,
|
||||
JIT_IMM_I64 = 0x400,
|
||||
/* argument can be a 32-bit or less float immediate */
|
||||
JIT_IMM_F32 = 0x200,
|
||||
JIT_IMM_F32 = 0x800,
|
||||
/* argument can be a 64-bit or less float immediate */
|
||||
JIT_IMM_F64 = 0x400,
|
||||
JIT_IMM_F64 = 0x1000,
|
||||
/* argument can be a block reference */
|
||||
JIT_IMM_BLK = 0x800,
|
||||
JIT_IMM_BLK = 0x2000,
|
||||
JIT_TYPE_MASK = JIT_REG_I64 | JIT_REG_F64 | JIT_REG_V128 | JIT_IMM_I32 |
|
||||
JIT_IMM_I64 | JIT_IMM_F32 | JIT_IMM_F64 | JIT_IMM_BLK,
|
||||
};
|
||||
|
|
|
@ -91,12 +91,14 @@ struct ra {
|
|||
|
||||
static int ra_reg_can_store(const struct jit_register *reg,
|
||||
const struct ir_value *v) {
|
||||
if (ir_is_int(v->type) && v->type <= VALUE_I64) {
|
||||
return reg->flags & JIT_REG_I64;
|
||||
} else if (ir_is_float(v->type) && v->type <= VALUE_F64) {
|
||||
return reg->flags & JIT_REG_F64;
|
||||
} else if (ir_is_vector(v->type) && v->type <= VALUE_V128) {
|
||||
return reg->flags & JIT_REG_V128;
|
||||
if (reg->flags & JIT_ALLOCATE) {
|
||||
if (ir_is_int(v->type) && v->type <= VALUE_I64) {
|
||||
return reg->flags & JIT_REG_I64;
|
||||
} else if (ir_is_float(v->type) && v->type <= VALUE_F64) {
|
||||
return reg->flags & JIT_REG_F64;
|
||||
} else if (ir_is_vector(v->type) && v->type <= VALUE_V128) {
|
||||
return reg->flags & JIT_REG_V128;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -222,7 +224,7 @@ static void ra_validate(struct ra *ra, struct ir *ir, struct ir_block *block) {
|
|||
for (int i = 0; i < ra->num_registers; i++) {
|
||||
const struct jit_register *reg = &ra->registers[i];
|
||||
|
||||
if (reg->flags & JIT_CALLER_SAVED) {
|
||||
if (reg->flags & JIT_CALLER_SAVE) {
|
||||
active[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +317,7 @@ static void ra_spill_tmps(struct ra *ra, struct ir *ir,
|
|||
/* only spill caller-saved regs */
|
||||
struct ra_bin *bin = ra_get_bin(tmp->value->reg);
|
||||
|
||||
if (!(bin->reg->flags & JIT_CALLER_SAVED)) {
|
||||
if (!(bin->reg->flags & JIT_CALLER_SAVE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -22,11 +22,12 @@ static const char *ta_vp =
|
|||
" // if w is negative, this vertex will always fail the clip test which is\n"
|
||||
" // defined as: -w <= z <= w\n"
|
||||
" // however, since OpenGL tries to maintain connectivity when a polygon is\n"
|
||||
" // clipped, a lot of junk vertices are generated in this case due to the z\n"
|
||||
" // component not being valid for our vertices. the best workaround so far\n"
|
||||
" // seems to be clamping w to always be positive, which doesn't explicitly\n"
|
||||
" // clip this vertex, but avoids generating additional junk ones\n"
|
||||
" w = max(w, 0.0);\n"
|
||||
" // clipped, a lot of junk vertices are generated. the best workaround so\n"
|
||||
" // far is to force xyz=0 to avoid generating visible junk vertices\n"
|
||||
" if (w < 0.0) {\n"
|
||||
" gl_Position = vec4(0.0, 0.0, 0.0, w);\n"
|
||||
" return;\n"
|
||||
" }\n"
|
||||
|
||||
" // scale x from [0,640] -> [-1,1] and y from [0,480] to [-1,1]\n"
|
||||
" gl_Position.xy = attr_xyz.xy * u_video_scale.xz + u_video_scale.yw;\n"
|
||||
|
|
15
src/ui.c
15
src/ui.c
|
@ -518,7 +518,8 @@ static void ui_scan_games_f(struct ui *ui, const char *filename) {
|
|||
snprintf(ui->scan_status, sizeof(ui->scan_status), "scanning %s", filename);
|
||||
|
||||
if (ui_has_game_ext(filename, game_exts, ARRAY_SIZE(game_exts))) {
|
||||
struct disc *disc = disc_create(filename);
|
||||
|
||||
struct disc *disc = disc_create(filename, 0);
|
||||
|
||||
if (disc) {
|
||||
struct game game = {0};
|
||||
|
@ -1099,7 +1100,7 @@ enum {
|
|||
};
|
||||
|
||||
static texture_handle_t ui_load_disc_texture(struct ui *ui, struct game *game) {
|
||||
struct disc *disc = disc_create(game->filename);
|
||||
struct disc *disc = disc_create(game->filename, 0);
|
||||
if (!disc) {
|
||||
return ui->disc_tex;
|
||||
}
|
||||
|
@ -1384,9 +1385,9 @@ void ui_set_page(struct ui *ui, int page_index) {
|
|||
}
|
||||
|
||||
/* trigger global callbacks for when the ui is open / closed */
|
||||
if (top_page == NULL && next_page != NULL) {
|
||||
if (!top_page && next_page) {
|
||||
ui_opened(ui->host);
|
||||
} else if (next_page == NULL) {
|
||||
} else if (!next_page) {
|
||||
ui_closed(ui->host);
|
||||
}
|
||||
}
|
||||
|
@ -1432,10 +1433,8 @@ int ui_keydown(struct ui *ui, int key, int16_t value) {
|
|||
/* prioritize canceling any open dialog */
|
||||
if (ui->dlg) {
|
||||
ui_close_dlg(ui, UI_DLG_CANCEL);
|
||||
}
|
||||
/* else, pop the history stack */
|
||||
else {
|
||||
ui->history_pos = MAX(ui->history_pos - 1, 1);
|
||||
} else if (ui->history_pos > 1) {
|
||||
ui->history_pos = ui->history_pos - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue