Compare commits

...

15 Commits

Author SHA1 Message Date
Ty
eee71d54ef Darwin: Implement Mach exceptions ports instead of signals
Credit goes to the Julia language and Dolphin emulator for their free and open source implementations
2026-01-01 13:33:34 -05:00
TheLastRar
43e073a18d GS/DX12: Fix validation errors when depth testing and sampling 2026-01-01 01:20:13 +01:00
JordanTheToaster
bc41666d53 GameDB: Various fixes 2026-01-01 01:09:27 +01:00
PCSX2 Bot
5b85d6a758 [ci skip] PAD: Update to latest controller database. 2025-12-29 17:04:38 +01:00
TheLastRar
1fdc000815 GS/DX12: Don't move to next command list until after wait 2025-12-29 17:04:28 +01:00
TheLastRar
3a57bb46ab GS/DX12: Correct descriptor allocation error messages 2025-12-26 16:20:43 +01:00
TheLastRar
2cb75e60b3 GS/DX12: Free from correct descriptor heap in error handling 2025-12-26 16:20:43 +01:00
TheLastRar
600ac6ec4f GS/DX12: Always require barrier of feedback read 2025-12-24 21:13:31 +01:00
JordanTheToaster
ed0cd628f8 GameDB: Warhammer 40k FW Fixes 2025-12-24 21:12:59 +01:00
JordanTheToaster
33a825c17f GameDB: Guitar Hero Smash Hits fixes 2025-12-23 09:44:52 -05:00
JordanTheToaster
660a165533 3rdparty: Update rcheevos to 12.2.0 2025-12-22 19:10:10 +01:00
TheLastRar
bfe2d5abb2 GS/HW: Split draws before modifying the index buffer 2025-12-21 17:44:21 +01:00
PCSX2 Bot
d5b36da6b0 [ci skip] Qt: Update Base Translation. 2025-12-21 14:35:27 +01:00
PCSX2 Bot
328cebd5fc [ci skip] Qt: Update Base Translation. 2025-12-20 14:07:05 +01:00
lightningterror
579cb7bd27 GS/HW: Use copies if barriers aren't supported properly.
VK/GL: Tex is fb requires the copy to be bound on slot 0 as well.
Fixes validation errors on VK when barriers are force disabled.

DX12: Always create a copy if barriers aren't supported for sw blend or tex is fb.
Fixes issues when barriers are force disabled.
2025-12-20 14:06:40 +01:00
31 changed files with 2629 additions and 1480 deletions

View File

@@ -1,3 +1,18 @@
# v12.2.0
* add rc_client_create_subset_list
* add rc_client_begin_fetch_game_titles
* greatly improve performance parsing long AddSource chains
* don't send pings if not processing frames; allows server to suspend session while emulator is paused
* modify validation logic to return most severe error instead of first error found
* improve validation warning when 'PauseIf {recall}' attempts to use non-PauseIf Remember
* fix rounding error when subtracting floats from integers
* fix infinite loop processing 'Remember {recall}' with no modifiers
* fix measured value jumping to 0 if all measured-generating alts are paused
* fix buffer overflow converting long user names between rc_client_external versions
* fix validation warning when adding differently sized values
* fix validation warning when ResetIf only applies to hit count inside an AndNext chain
* fix validation warning when only last node of modified memref chain differs
# v12.1.0
* add rc_client_get_user_subset_summary
* add validation warning for using MeasuredIf without Measured

View File

@@ -211,6 +211,8 @@ typedef struct rc_api_game_title_entry_t {
const char* title;
/* The image name for the game badge */
const char* image_name;
/* The URL for the game badge image */
const char* image_url;
}
rc_api_game_title_entry_t;

View File

@@ -364,6 +364,22 @@ RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client
RC_EXPORT void RC_CCONV rc_client_get_user_subset_summary(const rc_client_t* client, uint32_t subset_id, rc_client_user_game_summary_t* summary);
typedef struct rc_client_subset_list_t {
const rc_client_subset_t** subsets;
uint32_t num_subsets;
} rc_client_subset_list_t;
/**
* Creates a list of subsets for the currently loaded game.
* Returns an allocated list that must be free'd by calling rc_client_destroy_subset_list.
*/
RC_EXPORT rc_client_subset_list_t* RC_CCONV rc_client_create_subset_list(rc_client_t* client);
/**
* Destroys a list allocated by rc_client_create_subset_list_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_subset_list(rc_client_subset_list_t* list);
/*****************************************************************************\
| Fetch Game Hashes |
\*****************************************************************************/
@@ -398,6 +414,42 @@ RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_hash_library(
*/
RC_EXPORT void RC_CCONV rc_client_destroy_hash_library(rc_client_hash_library_t* list);
/*****************************************************************************\
| Fetch Game Titles |
\*****************************************************************************/
typedef struct rc_client_game_title_entry_t {
uint32_t game_id;
const char* title;
char badge_name[16];
const char* badge_url;
} rc_client_game_title_entry_t;
typedef struct rc_client_game_title_list_t {
rc_client_game_title_entry_t* entries;
uint32_t num_entries;
} rc_client_game_title_list_t;
/**
* Callback that is fired when a game titles request completes. list may be null if the query failed.
*/
typedef void(RC_CCONV* rc_client_fetch_game_titles_callback_t)(int result, const char* error_message,
rc_client_game_title_list_t* list, rc_client_t* client,
void* callback_userdata);
/**
* Starts an asynchronous request for titles and badge names for the specified games.
* The caller must provide an array of game IDs and the number of IDs in the array.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_game_titles(
rc_client_t* client, const uint32_t* game_ids, uint32_t num_game_ids,
rc_client_fetch_game_titles_callback_t callback, void* callback_userdata);
/**
* Destroys a previously-allocated result from the rc_client_begin_fetch_game_titles() callback.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_game_title_list(rc_client_game_title_list_t* list);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
@@ -503,7 +555,7 @@ enum {
RC_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping);
/**
* Destroys a list allocated by rc_client_get_achievement_list.
* Destroys a list allocated by rc_client_create_achievement_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);

View File

@@ -173,6 +173,8 @@ enum {
RC_OPERATOR_SUB,
RC_OPERATOR_SUB_PARENT, /* internal use */
RC_OPERATOR_ADD_ACCUMULATOR, /* internal use */
RC_OPERATOR_SUB_ACCUMULATOR, /* internal use */
RC_OPERATOR_INDIRECT_READ /* internal use */
};

View File

@@ -448,7 +448,8 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ImageIcon")
RC_JSON_NEW_FIELD("ImageIcon"),
RC_JSON_NEW_FIELD("ImageUrl")
};
memset(response, 0, sizeof(*response));
@@ -482,6 +483,10 @@ int rc_api_process_fetch_game_titles_server_response(rc_api_fetch_game_titles_re
if (!rc_json_get_required_string(&entry->image_name, &response->response, &entry_fields[2], "ImageIcon"))
return RC_MISSING_VALUE;
rc_json_get_optional_string(&entry->image_url, &response->response, &entry_fields[3], "ImageUrl", "");
if (!entry->image_url[0])
entry->image_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_GAME, entry->image_name);
++entry;
}
}

View File

@@ -1686,6 +1686,67 @@ static void rc_client_free_pending_media(rc_client_pending_media_t* pending_medi
free(pending_media);
}
static void rc_client_log_active_assets(rc_client_t* client)
{
uint32_t num_achievements;
uint32_t num_active_achievements;
uint32_t num_unsupported_achievements;
uint32_t num_leaderboards;
uint32_t num_unsupported_leaderboards;
const rc_client_achievement_info_t* ach;
const rc_client_achievement_info_t* ach_stop;
const rc_client_leaderboard_info_t* lbd;
const rc_client_leaderboard_info_t* lbd_stop;
const rc_client_subset_info_t* subset = client->game->subsets;
for (; subset; subset = subset->next) {
num_achievements = 0;
num_active_achievements = 0;
num_unsupported_achievements = 0;
num_leaderboards = 0;
num_unsupported_leaderboards = 0;
ach = subset->achievements;
ach_stop = ach + subset->public_.num_achievements;
for (; ach < ach_stop; ++ach) {
if (ach->public_.category == RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE) {
++num_achievements;
if (ach->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE)
++num_active_achievements;
else if (ach->public_.state == RC_CLIENT_ACHIEVEMENT_STATE_DISABLED)
++num_unsupported_achievements;
}
}
lbd = subset->leaderboards;
lbd_stop = lbd + subset->public_.num_leaderboards;
for (; lbd < lbd_stop; ++lbd) {
++num_leaderboards;
if (lbd->public_.state == RC_CLIENT_LEADERBOARD_STATE_DISABLED)
++num_unsupported_leaderboards;
}
if (num_unsupported_achievements) {
if (num_unsupported_leaderboards) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active (%u unsupported), %u leaderboards (%u unsupported)",
subset->public_.id, num_active_achievements, num_achievements, num_unsupported_achievements, num_leaderboards, num_unsupported_leaderboards);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active (%u unsupported), %u leaderboards",
subset->public_.id, num_active_achievements, num_achievements, num_unsupported_achievements, num_leaderboards);
}
}
else if (num_unsupported_leaderboards) {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active, %u leaderboards (%u unsupported)",
subset->public_.id, num_active_achievements, num_achievements, num_leaderboards, num_unsupported_leaderboards);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Set %u: %u/%u achievements active, %u leaderboards",
subset->public_.id, num_active_achievements, num_achievements, num_leaderboards);
}
}
}
/* NOTE: address validation uses the read_memory callback to make sure the client
* will return data for the requested address. As such, this function must
* respect the `client->state.allow_background_memory_reads setting. Use
@@ -1720,10 +1781,13 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
/* make the loaded game active if another game is not aleady being loaded. */
rc_mutex_lock(&client->state.mutex);
if (client->state.load == load_state)
if (client->state.load == load_state) {
client->game = load_state->game;
else
client->state.frames_processed = client->state.frames_at_last_ping = 0;
}
else {
load_state->progress = RC_CLIENT_LOAD_GAME_STATE_ABORTED;
}
rc_mutex_unlock(&client->state.mutex);
if (load_state->progress != RC_CLIENT_LOAD_GAME_STATE_ABORTED) {
@@ -1807,6 +1871,9 @@ static void rc_client_activate_game(rc_client_load_state_t* load_state, rc_api_s
RC_CLIENT_LOG_INFO_FORMATTED(client, "Game %u loaded, hardcore %s%s", load_state->game->public_.id,
client->state.hardcore ? "enabled" : "disabled",
(client->state.spectator_mode != RC_CLIENT_SPECTATOR_MODE_OFF) ? ", spectating" : "");
if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO)
rc_client_log_active_assets(client);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Subset %u loaded", load_state->subset->public_.id);
@@ -2352,6 +2419,7 @@ static int rc_client_attach_load_state(rc_client_t* client, rc_client_load_state
rc_mutex_lock(&client->state.mutex);
client->state.load = load_state;
client->state.frames_processed = client->state.frames_at_last_ping = 0;
rc_mutex_unlock(&client->state.mutex);
}
else if (client->state.load != load_state) {
@@ -3483,6 +3551,58 @@ const rc_client_subset_t* rc_client_get_subset_info(rc_client_t* client, uint32_
return NULL;
}
rc_client_subset_list_t* rc_client_create_subset_list(rc_client_t* client)
{
rc_client_subset_list_info_t* list;
const rc_client_subset_info_t* subset;
const rc_client_subset_t** subset_ptr;
const uint32_t list_size = RC_ALIGN(sizeof(*list));
uint32_t num_subsets = 0;
if (!client)
return (rc_client_subset_list_t*)calloc(1, list_size);
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
if (client->state.external_client && client->state.external_client->create_subset_list)
return (rc_client_subset_list_t*)client->state.external_client->create_subset_list();
#endif
if (!client->game)
return (rc_client_subset_list_t*)calloc(1, list_size);
rc_mutex_lock(&client->state.mutex);
subset = client->game->subsets;
for (; subset; subset = subset->next) {
if (subset->active)
num_subsets++;
}
list = (rc_client_subset_list_info_t*)malloc(list_size + num_subsets * sizeof(rc_client_subset_t*));
list->public_.subsets = subset_ptr = (const rc_client_subset_t**)((uint8_t*)list + list_size);
subset = client->game->subsets;
for (; subset; subset = subset->next) {
if (subset->active)
*subset_ptr++ = &subset->public_;
}
rc_mutex_unlock(&client->state.mutex);
list->destroy_func = NULL;
list->public_.num_subsets = (uint32_t)(subset_ptr - list->public_.subsets);
return &list->public_;
}
void rc_client_destroy_subset_list(rc_client_subset_list_t* list)
{
rc_client_subset_list_info_t* info = (rc_client_subset_list_info_t*)list;
if (info->destroy_func)
info->destroy_func(info);
else
free(list);
}
/* ===== Fetch Game Hashes ===== */
typedef struct rc_client_fetch_hash_library_callback_data_t {
@@ -3595,6 +3715,158 @@ void rc_client_destroy_hash_library(rc_client_hash_library_t* list)
free(list);
}
/* ===== Fetch Game Titles ===== */
typedef struct rc_client_fetch_game_titles_callback_data_t {
rc_client_t* client;
rc_client_fetch_game_titles_callback_t callback;
void* callback_userdata;
rc_client_async_handle_t async_handle;
} rc_client_fetch_game_titles_callback_data_t;
static void rc_client_fetch_game_titles_callback(const rc_api_server_response_t* server_response, void* callback_data)
{
rc_client_fetch_game_titles_callback_data_t* titles_callback_data =
(rc_client_fetch_game_titles_callback_data_t*)callback_data;
rc_client_t* client = titles_callback_data->client;
rc_api_fetch_game_titles_response_t titles_response;
const char* error_message;
int result;
result = rc_client_end_async(client, &titles_callback_data->async_handle);
if (result) {
if (result != RC_CLIENT_ASYNC_DESTROYED)
RC_CLIENT_LOG_VERBOSE(client, "Fetch game titles aborted");
free(titles_callback_data);
return;
}
result = rc_api_process_fetch_game_titles_server_response(&titles_response, server_response);
error_message =
rc_client_server_error_message(&result, server_response->http_status_code, &titles_response.response);
if (error_message) {
RC_CLIENT_LOG_ERR_FORMATTED(client, "Fetch game titles failed: %s", error_message);
titles_callback_data->callback(result, error_message, NULL, client, titles_callback_data->callback_userdata);
} else {
rc_client_game_title_list_t* list;
size_t strings_size = 0;
const rc_api_game_title_entry_t* src;
const rc_api_game_title_entry_t* stop;
size_t list_size;
/* calculate string buffer size */
for (src = titles_response.entries, stop = src + titles_response.num_entries; src < stop; ++src) {
if (src->title)
strings_size += strlen(src->title) + 1;
if (src->image_url)
strings_size += strlen(src->image_url) + 1;
}
list_size = sizeof(*list) + sizeof(rc_client_game_title_entry_t) * titles_response.num_entries + strings_size;
list = (rc_client_game_title_list_t*)malloc(list_size);
if (!list) {
titles_callback_data->callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client,
titles_callback_data->callback_userdata);
} else {
rc_client_game_title_entry_t* entry = list->entries =
(rc_client_game_title_entry_t*)((uint8_t*)list + sizeof(*list));
char* strings = (char*)((uint8_t*)list + sizeof(*list) +
sizeof(rc_client_game_title_entry_t) * titles_response.num_entries);
for (src = titles_response.entries, stop = src + titles_response.num_entries; src < stop; ++src, ++entry) {
entry->game_id = src->id;
if (src->title) {
const size_t len = strlen(src->title) + 1;
entry->title = strings;
memcpy(strings, src->title, len);
strings += len;
} else {
entry->title = NULL;
}
if (src->image_name)
snprintf(entry->badge_name, sizeof(entry->badge_name), "%s", src->image_name);
else
entry->badge_name[0] = '\0';
if (src->image_url) {
const size_t len = strlen(src->image_url) + 1;
entry->badge_url = strings;
memcpy(strings, src->image_url, len);
strings += len;
}
else {
entry->badge_url = NULL;
}
}
list->num_entries = titles_response.num_entries;
titles_callback_data->callback(RC_OK, NULL, list, client, titles_callback_data->callback_userdata);
}
}
rc_api_destroy_fetch_game_titles_response(&titles_response);
free(titles_callback_data);
}
rc_client_async_handle_t* rc_client_begin_fetch_game_titles(rc_client_t* client, const uint32_t* game_ids,
uint32_t num_game_ids,
rc_client_fetch_game_titles_callback_t callback,
void* callback_userdata)
{
rc_api_fetch_game_titles_request_t api_params;
rc_client_fetch_game_titles_callback_data_t* callback_data;
rc_client_async_handle_t* async_handle;
rc_api_request_t request;
int result;
const char* error_message;
if (!client) {
callback(RC_INVALID_STATE, "client is required", NULL, client, callback_userdata);
return NULL;
}
if (!game_ids || num_game_ids == 0) {
callback(RC_INVALID_STATE, "game_ids is required", NULL, client, callback_userdata);
return NULL;
}
api_params.game_ids = game_ids;
api_params.num_game_ids = num_game_ids;
result = rc_api_init_fetch_game_titles_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
error_message = rc_error_str(result);
callback(result, error_message, NULL, client, callback_userdata);
return NULL;
}
callback_data = (rc_client_fetch_game_titles_callback_data_t*)calloc(1, sizeof(*callback_data));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), NULL, client, callback_userdata);
return NULL;
}
callback_data->client = client;
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
async_handle = &callback_data->async_handle;
rc_client_begin_async(client, async_handle);
client->callbacks.server_call(&request, rc_client_fetch_game_titles_callback, callback_data, client);
rc_api_destroy_request(&request);
return rc_client_async_handle_valid(client, async_handle) ? async_handle : NULL;
}
void rc_client_destroy_game_title_list(rc_client_game_title_list_t* list)
{
free(list);
}
/* ===== Achievements ===== */
static void rc_client_update_achievement_display_information(rc_client_t* client, rc_client_achievement_info_t* achievement, time_t recent_unlock_time)
@@ -5160,30 +5432,37 @@ static void rc_client_ping(rc_client_scheduled_callback_data_t* callback_data, r
char buffer[256];
int result;
if (!client->callbacks.rich_presence_override ||
!client->callbacks.rich_presence_override(client, buffer, sizeof(buffer))) {
rc_mutex_lock(&client->state.mutex);
/* if no frames have been processed since the last ping, the emulator is idle. let the
* server session expire. it will be resumed/restarted once frames start getting
* processed again. */
if (client->state.frames_processed != client->state.frames_at_last_ping) {
client->state.frames_at_last_ping = client->state.frames_processed;
rc_runtime_get_richpresence(&client->game->runtime, buffer, sizeof(buffer),
client->state.legacy_peek, client, NULL);
if (!client->callbacks.rich_presence_override ||
!client->callbacks.rich_presence_override(client, buffer, sizeof(buffer))) {
rc_mutex_lock(&client->state.mutex);
rc_mutex_unlock(&client->state.mutex);
}
rc_runtime_get_richpresence(&client->game->runtime, buffer, sizeof(buffer),
client->state.legacy_peek, client, NULL);
memset(&api_params, 0, sizeof(api_params));
api_params.username = client->user.username;
api_params.api_token = client->user.token;
api_params.game_id = client->game->public_.id;
api_params.rich_presence = buffer;
api_params.game_hash = client->game->public_.hash;
api_params.hardcore = client->state.hardcore;
rc_mutex_unlock(&client->state.mutex);
}
result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Error generating ping request: %s", rc_error_str(result));
}
else {
client->callbacks.server_call(&request, rc_client_ping_callback, client, client);
memset(&api_params, 0, sizeof(api_params));
api_params.username = client->user.username;
api_params.api_token = client->user.token;
api_params.game_id = client->game->public_.id;
api_params.rich_presence = buffer;
api_params.game_hash = client->game->public_.hash;
api_params.hardcore = client->state.hardcore;
result = rc_api_init_ping_request_hosted(&request, &api_params, &client->state.host);
if (result != RC_OK) {
RC_CLIENT_LOG_WARN_FORMATTED(client, "Error generating ping request: %s", rc_error_str(result));
}
else {
client->callbacks.server_call(&request, rc_client_ping_callback, client, client);
}
}
callback_data->when = now + 120 * 1000;
@@ -5885,6 +6164,8 @@ void rc_client_do_frame(rc_client_t* client)
rc_mutex_unlock(&client->state.mutex);
rc_client_raise_pending_events(client, client->game);
++client->state.frames_processed;
}
/* we've processed a frame. if there's a pause delay in effect, process it */

View File

@@ -9,13 +9,15 @@
/* https://media.retroachievements.org/Badge/123456_lock.png is 58 with null terminator */
#define RC_CLIENT_IMAGE_URL_BUFFER_SIZE 64
/* https://media.retroachievements.org/UserPic/TwentyCharUserNameXX.png is 69 with null terminator */
#define RC_CLIENT_USER_IMAGE_URL_BUFFER_SIZE 80
typedef struct rc_client_external_conversions_t {
rc_client_user_t user;
rc_client_game_t game;
rc_client_subset_t subsets[4];
rc_client_achievement_t achievements[16];
char user_avatar_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char user_avatar_url[RC_CLIENT_USER_IMAGE_URL_BUFFER_SIZE];
char game_badge_url[RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char subset_badge_url[4][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
char achievement_badge_url[16][RC_CLIENT_IMAGE_URL_BUFFER_SIZE];
@@ -24,7 +26,7 @@ typedef struct rc_client_external_conversions_t {
uint32_t next_achievement_index;
} rc_client_external_conversions_t;
static const char* rc_client_external_build_avatar_url(char buffer[], uint32_t image_type, const char* image_name)
static const char* rc_client_external_build_avatar_url(char buffer[], size_t buffer_size, uint32_t image_type, const char* image_name)
{
rc_api_fetch_image_request_t image_request;
rc_api_request_t request;
@@ -38,7 +40,7 @@ static const char* rc_client_external_build_avatar_url(char buffer[], uint32_t i
if (result != RC_OK)
return NULL;
strcpy_s(buffer, RC_CLIENT_IMAGE_URL_BUFFER_SIZE, request.url);
snprintf(buffer, buffer_size, "%s", request.url);
return buffer;
}
@@ -69,7 +71,9 @@ const rc_client_user_t* rc_client_external_convert_v1_user(const rc_client_t* cl
RC_CONVERSION_FILL(converted, rc_client_user_t, v1_rc_client_user_t);
converted->avatar_url = rc_client_external_build_avatar_url(
client->state.external_client_conversions->user_avatar_url, RC_IMAGE_TYPE_USER, v1_user->username);
client->state.external_client_conversions->user_avatar_url,
sizeof(client->state.external_client_conversions->user_avatar_url),
RC_IMAGE_TYPE_USER, v1_user->username);
return converted;
}
@@ -88,7 +92,9 @@ const rc_client_game_t* rc_client_external_convert_v1_game(const rc_client_t* cl
RC_CONVERSION_FILL(converted, rc_client_game_t, v1_rc_client_game_t);
converted->badge_url = rc_client_external_build_avatar_url(
client->state.external_client_conversions->game_badge_url, RC_IMAGE_TYPE_GAME, v1_game->badge_name);
client->state.external_client_conversions->game_badge_url,
sizeof(client->state.external_client_conversions->game_badge_url),
RC_IMAGE_TYPE_GAME, v1_game->badge_name);
return converted;
}
@@ -123,7 +129,9 @@ const rc_client_subset_t* rc_client_external_convert_v1_subset(const rc_client_t
memcpy(converted, v1_subset, sizeof(v1_rc_client_subset_t));
RC_CONVERSION_FILL(converted, rc_client_subset_t, v1_rc_client_subset_t);
converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
converted->badge_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->subset_badge_url[0]),
RC_IMAGE_TYPE_GAME, v1_subset->badge_name);
return converted;
}
@@ -161,8 +169,12 @@ const rc_client_achievement_t* rc_client_external_convert_v1_achievement(const r
memcpy(converted, v1_achievement, sizeof(v1_rc_client_achievement_t));
RC_CONVERSION_FILL(converted, rc_client_achievement_t, v1_rc_client_achievement_t);
converted->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
converted->badge_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->achievement_badge_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT, v1_achievement->badge_name);
converted->badge_locked_url = rc_client_external_build_avatar_url(badge_locked_url,
sizeof(client->state.external_client_conversions->achievement_badge_locked_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, v1_achievement->badge_name);
return converted;
}
@@ -246,9 +258,13 @@ rc_client_achievement_list_t* rc_client_external_convert_v1_achievement_list(con
*achievement = &new_list->achievements[num_achievements++];
memcpy(*achievement, *src_achievement, sizeof(**src_achievement));
(*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
(*achievement)->badge_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->achievement_badge_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT, (*achievement)->badge_name);
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
(*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url, RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
(*achievement)->badge_locked_url = rc_client_external_build_avatar_url(badge_url,
sizeof(client->state.external_client_conversions->achievement_badge_locked_url[0]),
RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED, (*achievement)->badge_name);
badge_url += RC_CLIENT_IMAGE_URL_BUFFER_SIZE;
}
}

View File

@@ -61,6 +61,11 @@ typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_lead
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t)(rc_client_t* client,
uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
/* NOTE: rc_client_external_create_subset_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
struct rc_client_subset_list_info_t;
typedef struct rc_client_subset_list_info_t* (RC_CCONV* rc_client_external_create_subset_list_func_t)();
typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer, size_t buffer_size);
@@ -144,6 +149,9 @@ typedef struct rc_client_external_t
rc_client_external_get_user_game_summary_func_t get_user_game_summary_v5;
rc_client_external_get_user_subset_summary_func_t get_user_subset_summary;
/* VERSION 6 */
rc_client_external_create_subset_list_func_t create_subset_list;
} rc_client_external_t;
#define RC_CLIENT_EXTERNAL_VERSION 5

View File

@@ -222,6 +222,14 @@ typedef struct rc_client_subset_info_t {
uint8_t pending_events;
} rc_client_subset_info_t;
struct rc_client_subset_list_info_t;
typedef void (RC_CCONV* rc_client_destroy_subset_list_func_t)(struct rc_client_subset_list_info_t* list);
typedef struct rc_client_subset_list_info_t {
rc_client_subset_list_t public_;
rc_client_destroy_subset_list_func_t destroy_func;
} rc_client_subset_list_info_t;
/*****************************************************************************\
| Game |
\*****************************************************************************/
@@ -316,6 +324,8 @@ typedef struct rc_client_state_t {
rc_client_raintegration_t* raintegration;
#endif
uint32_t frames_processed;
uint32_t frames_at_last_ping;
uint16_t unpaused_frame_decay;
uint16_t required_unpaused_frames;

View File

@@ -649,6 +649,7 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi
uint32_t i, j;
rc_libretro_core_memory_info_t info;
size_t offset;
int found_aligning_padding = 0;
for (i = 0; i < console_regions->num_regions; ++i) {
const rc_memory_region_t* console_region = &console_regions->region[i];
@@ -656,6 +657,16 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi
const uint32_t type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
uint32_t base_address = 0;
if (console_region->type == RC_MEMORY_TYPE_UNUSED && console_region_size >= 0x10000 && !found_aligning_padding) {
if (console_regions->region[console_regions->num_regions - 1].end_address > 0x01000000) {
/* assume anything exposing more than 16MB of regions with at least one 64KB+ UNUSED region
* is padding so things align with real addresses. this indicates the memory is disjoint
* in the system, so we cannot expect it to be contiguous in the RETRO_SYSTEM_RAM.
* stop processing regions now, and just fill the remaining memory map with null filler. */
found_aligning_padding = 1;
}
}
for (j = 0; j <= i; ++j) {
const rc_memory_region_t* console_region2 = &console_regions->region[j];
if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
@@ -665,7 +676,13 @@ static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regi
}
offset = console_region->start_address - base_address;
get_core_memory_info(type, &info);
if (!found_aligning_padding) {
get_core_memory_info(type, &info);
}
else {
info.data = NULL;
info.size = console_region_size;
}
if (offset < info.size) {
info.size -= offset;

View File

@@ -8,7 +8,7 @@
RC_BEGIN_C_DECLS
#define RCHEEVOS_VERSION_MAJOR 12
#define RCHEEVOS_VERSION_MINOR 1
#define RCHEEVOS_VERSION_MINOR 2
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)

View File

@@ -374,7 +374,7 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
}
parse->addsource_oper = RC_OPERATOR_ADD;
parse->addsource_oper = RC_OPERATOR_ADD_ACCUMULATOR;
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
@@ -388,13 +388,13 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
/* type determined by parent */
const uint8_t new_size = rc_operand_is_float(&parse->addsource_parent) ? RC_MEMSIZE_FLOAT : RC_MEMSIZE_32_BITS;
if (parse->addsource_oper == RC_OPERATOR_ADD && !rc_operand_is_memref(&parse->addsource_parent)) {
if (parse->addsource_oper == RC_OPERATOR_ADD_ACCUMULATOR && !rc_operand_is_memref(&parse->addsource_parent)) {
/* if the previous element was a constant we have to turn it into a memref by adding zero */
rc_modified_memref_t* memref;
rc_operand_t zero;
rc_operand_set_const(&zero, 0);
memref = rc_alloc_modified_memref(parse,
parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD, &zero);
parse->addsource_parent.size, &parse->addsource_parent, RC_OPERATOR_ADD_ACCUMULATOR, &zero);
parse->addsource_parent.value.memref = (rc_memref_t*)memref;
parse->addsource_parent.type = RC_OPERAND_ADDRESS;
}
@@ -414,19 +414,27 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
}
/* subtract the condition from the chain */
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB : RC_OPERATOR_SUB_PARENT;
parse->addsource_oper = rc_operand_is_memref(&parse->addsource_parent) ? RC_OPERATOR_SUB_ACCUMULATOR : RC_OPERATOR_SUB_PARENT;
rc_condition_convert_to_operand(condition, &cond_operand, parse);
rc_operand_addsource(&cond_operand, parse, new_size);
memcpy(&parse->addsource_parent, &cond_operand, sizeof(cond_operand));
/* indicate the next value can be added to the chain */
parse->addsource_oper = RC_OPERATOR_ADD;
parse->addsource_oper = RC_OPERATOR_ADD_ACCUMULATOR;
}
parse->indirect_parent.type = RC_OPERAND_NONE;
break;
case RC_CONDITION_REMEMBER:
if (condition->operand1.type == RC_OPERAND_RECALL &&
condition->oper == RC_OPERATOR_NONE &&
parse->addsource_parent.type == RC_OPERAND_NONE &&
parse->indirect_parent.type == RC_OPERAND_NONE) {
/* Remembering {recall} without any modifications is a no-op */
break;
}
rc_condition_convert_to_operand(condition, &condition->operand1, parse);
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
@@ -465,6 +473,9 @@ void rc_condition_update_parse_state(rc_condition_t* condition, rc_parse_state_t
default:
if (parse->addsource_parent.type != RC_OPERAND_NONE) {
/* type determined by leaf */
if (parse->addsource_oper == RC_OPERATOR_ADD_ACCUMULATOR)
parse->addsource_oper = RC_OPERATOR_ADD;
rc_operand_addsource(&condition->operand1, parse, condition->operand1.size);
condition->operand1.is_combining = 1;

View File

@@ -62,8 +62,10 @@ static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr, c
do {
rc_parse_condition_internal(&condition, &memaddr, &parse);
if (parse.offset < 0)
if (parse.offset < 0) {
rc_destroy_parse_state(&parse);
return parse.offset;
}
++index;
@@ -106,7 +108,9 @@ static int32_t rc_classify_conditions(rc_condset_t* self, const char* memaddr, c
* logic in rc_find_next_classification */
self->num_other_conditions += chain_length - 1;
return index;
rc_destroy_parse_state(&parse);
return (int32_t)index;
}
static int rc_find_next_classification(const char* memaddr) {

View File

@@ -154,8 +154,14 @@ rc_modified_memref_t* rc_alloc_modified_memref(rc_parse_state_t* parse, uint8_t
memcpy(&modified_memref->parent, parent, sizeof(modified_memref->parent));
memcpy(&modified_memref->modifier, modifier, sizeof(modified_memref->modifier));
modified_memref->modifier_type = modifier_type;
modified_memref->depth = 0;
modified_memref->memref.address = rc_operand_is_memref(modifier) ? modifier->value.memref->address : modifier->value.num;
if (rc_operand_is_memref(parent) && parent->value.memref->value.memref_type == RC_MEMREF_TYPE_MODIFIED_MEMREF) {
const rc_modified_memref_t* parent_modified_memref = (rc_modified_memref_t*)parent->value.memref;
modified_memref->depth = parent_modified_memref->depth + 1;
}
return modified_memref;
}
@@ -729,11 +735,34 @@ uint32_t rc_get_modified_memref_value(const rc_modified_memref_t* memref, rc_pee
break;
case RC_OPERATOR_SUB_PARENT:
/* sub parent is "-parent + modifier" */
rc_typed_value_negate(&value);
rc_typed_value_add(&value, &modifier);
rc_typed_value_convert(&value, memref->memref.value.type);
break;
case RC_OPERATOR_SUB_ACCUMULATOR:
rc_typed_value_negate(&modifier);
/* fallthrough */ /* to case RC_OPERATOR_SUB_ACCUMULATOR */
case RC_OPERATOR_ADD_ACCUMULATOR:
/* when modifying the accumulator, force the modifier to match the accumulator
* type instead of promoting them both to the less restrictive type.
*
* 18 - 17.5 will result in an integer. should it be 0 or 1?
*
* default: float is less restrictive, convert both to float for combine,
* then convert to the memref type.
* (int)((float)18 - 17.5) -> (int)(0.5) -> 0
*
* accumulator is integer: force modifier to be integer before combining
* (int)(18 - (int)17.5) -> (int)(18 - 17) -> 1
*/
rc_typed_value_convert(&modifier, value.type);
rc_typed_value_add(&value, &modifier);
rc_typed_value_convert(&value, memref->memref.value.type);
break;
default:
rc_typed_value_combine(&value, &modifier, memref->modifier_type);
rc_typed_value_convert(&value, memref->memref.value.type);

View File

@@ -334,8 +334,11 @@ int rc_operands_are_equal(const rc_operand_t* left, const rc_operand_t* right) {
const rc_modified_memref_t* left_memref = (const rc_modified_memref_t*)left->value.memref;
const rc_modified_memref_t* right_memref = (const rc_modified_memref_t*)right->value.memref;
return (left_memref->modifier_type == right_memref->modifier_type &&
left_memref->depth == right_memref->depth &&
rc_operands_are_equal(&left_memref->modifier, &right_memref->modifier) &&
rc_operands_are_equal(&left_memref->parent, &right_memref->parent) &&
rc_operands_are_equal(&left_memref->modifier, &right_memref->modifier));
1 == 1
);
}
default:

View File

@@ -14,10 +14,11 @@ typedef struct rc_scratch_string {
rc_scratch_string_t;
typedef struct rc_modified_memref_t {
rc_memref_t memref; /* for compatibility with rc_operand_t.value.memref */
rc_memref_t memref; /* For compatibility with rc_operand_t.value.memref */
rc_operand_t parent; /* The parent memref this memref is derived from (type will always be a memref type) */
rc_operand_t modifier; /* The modifier to apply to the parent. */
uint8_t modifier_type; /* How to apply the modifier to the parent. (RC_OPERATOR_*) */
uint16_t depth; /* The number of parents this memref has. */
}
rc_modified_memref_t;
@@ -382,7 +383,6 @@ rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self);
void rc_reset_richpresence_triggers(rc_richpresence_t* self);
void rc_update_richpresence_internal(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud);
int rc_validate_memrefs(const rc_memrefs_t* memrefs, char result[], const size_t result_size, uint32_t max_address);
int rc_validate_memrefs_for_console(const rc_memrefs_t* memrefs, char result[], const size_t result_size, uint32_t console_id);
RC_END_C_DECLS

File diff suppressed because it is too large Load Diff

View File

@@ -235,8 +235,14 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, void* unus
is_paused |= sub_paused;
}
/* if paused, the measured value may not be captured, keep the old value */
if (!is_paused) {
if (is_paused) {
/* if the trigger is fully paused, ignore any updates to the measured value */
}
else if (measured_value.type == RC_VALUE_TYPE_NONE) {
/* if a measured value was not captured, keep the old value (it's possible to pause
* an alt that is generating the measured value without fully pausing the trigger) */
}
else {
rc_typed_value_convert(&measured_value, RC_VALUE_TYPE_UNSIGNED);
self->measured_value = measured_value.value.u32;
}

View File

@@ -67,6 +67,7 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
next_clause = &self->conditions;
do {
/* count the number of joiners and add one to determine the number of clauses. */
buffer[0] = 'A'; /* reset to AddSource */
done = 0;
num_measured_conditions = 1;
buffer_ptr = *memaddr;
@@ -97,8 +98,8 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
}
} while (!done);
/* if last condition is SubSource, we'll need to add a dummy condition for the Measured */
if (buffer[0] == 'B')
/* if last condition is not AddSource, we'll need to add a dummy condition for the Measured */
if (buffer[0] != 'A')
++num_measured_conditions;
condset_with_conditions = RC_ALLOC_WITH_TRAILING(rc_condset_with_trailing_conditions_t,
@@ -121,10 +122,18 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
for (;; ++(*memaddr)) {
switch (**memaddr) {
case '_': /* add next */
*ptr = '\0';
break;
case '$': /* maximum of */
case '\0': /* end of string */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
/* the last condition needs to be Measured - AddSource can be changed here,
* SubSource will be handled later */
if (buffer[0] == 'A')
buffer[0] = 'M';
*ptr = '\0';
break;
@@ -176,33 +185,34 @@ static void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_par
return;
}
rc_condition_update_parse_state(cond, parse);
*next = cond;
next = &cond->next;
if (**memaddr != '_') /* add next */
break;
rc_condition_update_parse_state(cond, parse);
++cond;
}
/* end of clause */
if (cond->type == RC_CONDITION_SUB_SOURCE) {
/* cannot change SubSource to Measured. add a dummy condition */
rc_condition_update_parse_state(cond, parse);
if (parse->buffer)
/* -- end of clause -- */
/* clause must end in a Measured. if it doesn't, append one */
if (cond->type != RC_CONDITION_MEASURED) {
if (!parse->buffer)
cond = &local_cond;
else
++cond;
buffer_ptr = "A:0";
buffer_ptr = "M:0";
rc_parse_condition_internal(cond, &buffer_ptr, parse);
*next = cond;
next = &cond->next;
rc_condition_update_parse_state(cond, parse);
}
/* convert final AddSource condition to Measured */
cond->type = RC_CONDITION_MEASURED;
cond->next = NULL;
rc_condition_update_parse_state(cond, parse);
*next = NULL;
/* finalize clause */
*next_clause = condset;

View File

@@ -1300,7 +1300,18 @@ static void rc_hash_initialize_iterator_from_path(rc_hash_iterator_t* iterator,
bsearch(&search, handlers, num_handlers, sizeof(*handler), rc_hash_iterator_find_handler);
if (handler) {
handler->handler(iterator, handler->data);
} else {
if (iterator->callbacks.verbose_message) {
int count = 0;
while (iterator->consoles[count])
++count;
rc_hash_iterator_verbose_formatted(iterator, "Found %d potential consoles for %s file extension", count, ext);
}
}
else {
rc_hash_iterator_error_formatted(iterator, "No console mapping specified for %s file extension - trying full file hash", ext);
/* if we didn't match the extension, default to something that does a whole file hash */
if (!iterator->consoles[0])
iterator->consoles[0] = RC_CONSOLE_GAMEBOY;
@@ -1332,15 +1343,6 @@ int rc_hash_iterate(char hash[33], rc_hash_iterator_t* iterator) {
if (iterator->index == -1) {
rc_hash_initialize_iterator_from_path(iterator, iterator->path);
if (iterator->callbacks.verbose_message) {
int count = 0;
while (iterator->consoles[count])
++count;
rc_hash_iterator_verbose_formatted(iterator, "Found %d potential consoles for %s file extension", count, rc_path_get_extension(iterator->path));
}
iterator->index = 0;
}

View File

@@ -13722,6 +13722,8 @@ SLED-51676:
region: "PAL"
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLED-51807:
name: "Summer Heat Beach Volleyball"
region: "PAL-Unk"
@@ -16672,6 +16674,8 @@ SLES-50958:
region: "PAL-M4"
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLES-50963:
name: "Riding Spirits"
region: "PAL-M5"
@@ -17826,6 +17830,8 @@ SLES-51393:
SLES-51397:
name: "IndyCar Series"
region: "PAL-M5"
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLES-51398:
name: "World Championship Snooker 2003"
region: "PAL-E"
@@ -19949,6 +19955,8 @@ SLES-52298:
name: "IndyCar Series 2005"
region: "PAL-M5"
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLES-52308:
name: "Karaoke Stage"
region: "PAL-M5"
@@ -20119,6 +20127,9 @@ SLES-52378:
name: "Euro Rally Champion"
region: "PAL-M5"
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
textureInsideRT: 1 # Fixes broken fog rendering.
SLES-52379:
name: "Shrek 2"
region: "PAL-E"
@@ -25053,6 +25064,8 @@ SLES-53957:
SLES-53958:
name: "Noble Racing"
region: "PAL-E"
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLES-53959:
name: "Pac-Man World 3"
region: "PAL-M5"
@@ -30130,6 +30143,11 @@ SLES-55544:
region: "PAL-M5"
roundModes:
vu1RoundMode: 0 # Fixes VU size spam and potential graphical issues with GH3 engine.
gsHWFixes:
cpuCLUTRender: 1 # Fixes broken rainbow rendering.
halfPixelOffset: 4 # Mostly aligns post-processing.
nativeScaling: 1 # Fixes post-processing smoothness and position.
autoFlush: 1 # Fixes edge garbage and shadow definition.
SLES-55545:
name: "WWE SmackDown! vs. Raw 2010"
region: "PAL-M5"
@@ -66750,6 +66768,8 @@ SLUS-20597:
compat: 5
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLUS-20598:
name: "Everblue 2"
region: "NTSC-U"
@@ -66973,6 +66993,8 @@ SLUS-20641:
name: "IndyCar Series"
region: "NTSC-U"
compat: 5
gsHWFixes:
halfPixelOffset: 5 # Aligns post-processing and fixes depth line.
SLUS-20642:
name: "Auto Modellista"
region: "NTSC-U"
@@ -74178,8 +74200,10 @@ SLUS-21866:
roundModes:
vu1RoundMode: 0 # Fixes VU size spam and potential graphical issues with GH3 engine.
gsHWFixes:
cpuCLUTRender: 1 # Fixes broken rainbow rendering.
halfPixelOffset: 4 # Mostly aligns post-processing.
nativeScaling: 1 # Fixes post-processing smoothness and position.
autoFlush: 1 # Fixes edge garbage and shadow definition.
SLUS-21867:
name: "Guitar Hero - Van Halen"
region: "NTSC-U"
@@ -74977,6 +75001,8 @@ SLUS-29049:
region: "NTSC-U"
gameFixes:
- XGKickHack # Fixes corrupted graphics.
gsHWFixes:
autoFlush: 1 # Fixes light bleed through walls.
SLUS-29050:
name: "EverQuest Online Adventures [Regular Demo]"
region: "NTSC-U"

View File

@@ -850,6 +850,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
# Mac OS X
030000008f0e00000300000009010000,2 In 1 Joystick,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,platform:Mac OS X,
03000000c82d00001930000000000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001930000000020000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00001930000001000000,8BitDo 64,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,righttrigger:b9,rightx:a2,righty:a3,start:b11,platform:Mac OS X,
03000000c82d00000031000001000000,8BitDo Adapter,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,
@@ -895,7 +896,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
03000000c82d00001290000001000000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00004028000000010000,8BitDo SN30,a:b1,b:b0,back:b10,dpdown:+a1,dpleft:-a0,dpright:+a0,dpup:-a1,leftshoulder:b6,rightshoulder:b7,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000160000001000000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b6,leftstick:b13,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a5,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000161000000010000,8BitDo SN30 Pro,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000260000001000000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00000261000000010000,8BitDo SN30 Pro Plus,a:b1,b:b0,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b2,leftshoulder:b6,leftstick:b13,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:b9,rightx:a2,righty:a3,start:b11,x:b4,y:b3,platform:Mac OS X,
03000000c82d00001230000000010000,8BitDo Ultimate,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,paddle1:b2,paddle2:b5,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Mac OS X,

View File

@@ -11,6 +11,7 @@
#include "common/Threading.h"
#include "common/WindowInfo.h"
#include "common/HostSys.h"
#include "fmt/format.h"
#include <csignal>
#include <cstring>
@@ -19,12 +20,15 @@
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <thread>
#include <time.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/mach_time.h>
#include <mach/mach_vm.h>
#include <mach/message.h>
#include <mach/task.h>
#include <mach/thread_state.h>
#include <mach/vm_map.h>
#include <mutex>
#include <ApplicationServices/ApplicationServices.h>
@@ -294,7 +298,7 @@ static CPUInfo CalcCPUInfo()
std::vector<DarwinMisc::CPUClass> classes = DarwinMisc::GetCPUClasses();
out.num_clusters = static_cast<u32>(classes.size());
out.num_big_cores = classes.empty() ? 0 : classes[0].num_physical;
out.num_threads = classes.empty() ? 0 : classes[0].num_logical;
out.num_threads = classes.empty() ? 0 : classes[0].num_logical;
out.num_small_cores = 0;
for (std::size_t i = 1; i < classes.size(); i++)
{
@@ -568,15 +572,206 @@ void HostSys::EndCodeWrite()
#endif // _M_ARM64
#define USE_MACH_EXCEPTION_PORTS
namespace PageFaultHandler
{
#ifdef USE_MACH_EXCEPTION_PORTS
static void SignalHandler(mach_port_t port);
#else
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
#endif
static std::recursive_mutex s_exception_handler_mutex;
static bool s_in_exception_handler = false;
static bool s_installed = false;
} // namespace PageFaultHandler
#ifdef USE_MACH_EXCEPTION_PORTS
#if defined(_M_X86)
#define THREAD_STATE64_COUNT x86_THREAD_STATE64_COUNT
#define THREAD_STATE64 x86_THREAD_STATE64
#define thread_state64_t x86_thread_state64_t
#elif defined(_M_ARM64)
#define THREAD_STATE64_COUNT ARM_THREAD_STATE64_COUNT
#define THREAD_STATE64 ARM_THREAD_STATE64
#define thread_state64_t arm_thread_state64_t
#else
#error Unknown Darwin Platform
#endif
void PageFaultHandler::SignalHandler(mach_port_t port)
{
Threading::SetNameOfCurrentThread("Mach Exception Thread");
#pragma pack(4)
struct
{
mach_msg_header_t Head;
NDR_record_t NDR;
exception_type_t exception;
mach_msg_type_number_t codeCnt;
int64_t code[2];
int flavor;
mach_msg_type_number_t old_stateCnt;
natural_t old_state[THREAD_STATE64_COUNT];
mach_msg_trailer_t trailer;
} msg_in;
struct
{
mach_msg_header_t Head;
NDR_record_t NDR;
kern_return_t RetCode;
int flavor;
mach_msg_type_number_t new_stateCnt;
natural_t new_state[THREAD_STATE64_COUNT];
} msg_out;
#pragma pack()
memset(&msg_in, 0xee, sizeof(msg_in));
memset(&msg_out, 0xee, sizeof(msg_out));
mach_msg_size_t send_size = 0;
mach_msg_option_t option = MACH_RCV_MSG;
while (true)
{
kern_return_t r;
if ((r = mach_msg_overwrite(&msg_out.Head, option, send_size, sizeof(msg_in), port,
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL, &msg_in.Head, 0)))
{
pxFail(fmt::format("CRITICAL: mach_msg_overwrite: {:x}", r).c_str());
}
if (msg_in.Head.msgh_id == MACH_NOTIFY_NO_SENDERS)
{
// the other thread exited
mach_port_deallocate(mach_task_self(), port);
return;
}
if (msg_in.Head.msgh_id != 2406)
{
pxFailRel("unknown message received");
return;
}
if (msg_in.flavor != THREAD_STATE64)
{
pxFailRel(fmt::format("unknown flavour {}, expected {}", msg_in.flavor, THREAD_STATE64).c_str());
return;
}
s_exception_handler_mutex.lock();
thread_state64_t* state = (thread_state64_t*)msg_in.old_state;
HandlerResult result = HandlerResult::ExecuteNextHandler;
if (!s_in_exception_handler)
{
s_in_exception_handler = true;
#ifdef _M_ARM64
result = HandlePageFault(reinterpret_cast<void*>(state->__pc), reinterpret_cast<void*>(msg_in.code[1]), (msg_in.code[0] & 2) != 0);
#else
result = HandlePageFault(reinterpret_cast<void*>(state->__rip), reinterpret_cast<void*>(msg_in.code[1]), (msg_in.code[0] & 2) != 0);
#endif
s_in_exception_handler = false;
}
// Set up the reply.
msg_out.Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(msg_in.Head.msgh_bits), 0);
msg_out.Head.msgh_remote_port = msg_in.Head.msgh_remote_port;
msg_out.Head.msgh_local_port = MACH_PORT_NULL;
msg_out.Head.msgh_id = msg_in.Head.msgh_id + 100;
msg_out.NDR = msg_in.NDR;
if (result != HandlerResult::ContinueExecution) // cooked
{
msg_out.RetCode = KERN_FAILURE;
msg_out.flavor = 0;
msg_out.new_stateCnt = 0;
// The crash handler on macOS or Linux doesn't use context passed to it
// Stubbing it here is fine
CrashHandler::CrashSignalHandler(-1, nullptr, nullptr);
pxFailRel("CrashSignalHandler returned when it should have terminated us!");
}
else
{
// Resumes execution right where we left off (re-executes instruction that caused the SIGSEGV)
msg_out.RetCode = KERN_SUCCESS;
msg_out.flavor = THREAD_STATE64;
msg_out.new_stateCnt = THREAD_STATE64_COUNT;
memcpy(msg_out.new_state, msg_in.old_state, THREAD_STATE64_COUNT * sizeof(natural_t));
}
msg_out.Head.msgh_size =
offsetof(__typeof__(msg_out), new_state) + msg_out.new_stateCnt * sizeof(natural_t);
send_size = msg_out.Head.msgh_size;
option |= MACH_SEND_MSG;
s_exception_handler_mutex.unlock();
}
}
bool PageFaultHandler::Install(Error* error)
{
exception_mask_t masks[EXC_TYPES_COUNT];
mach_port_t ports[EXC_TYPES_COUNT];
exception_behavior_t behaviors[EXC_TYPES_COUNT];
thread_state_flavor_t flavors[EXC_TYPES_COUNT];
mach_msg_type_number_t count = EXC_TYPES_COUNT;
kern_return_t r = task_get_exception_ports(mach_task_self(), EXC_MASK_ALL,
masks, &count, ports, behaviors, flavors);
mach_port_t port;
if ((r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)))
{
pxFailRel(fmt::format("mach_port_allocate: {:x}", r).c_str());
return false;
}
std::thread sig_thread(PageFaultHandler::SignalHandler, port);
sig_thread.detach();
if ((r = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("mach_port_insert_right: {:x}", r).c_str());
return false;
}
task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, THREAD_STATE_NONE);
if ((r = thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, port, EXCEPTION_STATE | MACH_EXCEPTION_CODES, THREAD_STATE64)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("thread_set_exception_ports: {:x}", r).c_str());
return false;
}
if ((r = mach_port_mod_refs(mach_task_self(), port, MACH_PORT_RIGHT_SEND, -1)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("mach_port_mod_refs: {:x}", r).c_str());
return false;
}
mach_port_t previous;
if ((r = mach_port_request_notification(mach_task_self(), port, MACH_NOTIFY_NO_SENDERS, 0, port, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous)))
{
mach_port_deallocate(mach_task_self(), port);
pxFailRel(fmt::format("mach_port_mod_refs: {:x}", r).c_str());
return false;
}
s_installed = true;
return true;
}
#else
void PageFaultHandler::SignalHandler(int sig, siginfo_t* info, void* ctx)
{
#if defined(_M_X86)
@@ -644,3 +839,4 @@ bool PageFaultHandler::Install(Error* error)
s_installed = true;
return true;
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -343,12 +343,14 @@ bool GSDevice12::CreateCommandLists()
void GSDevice12::MoveToNextCommandList()
{
m_current_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
m_current_fence_value++;
const int next_command_list = (m_current_command_list + 1) % NUM_COMMAND_LISTS;
// We may have to wait if this command list hasn't finished on the GPU.
CommandListResources& res = m_command_lists[m_current_command_list];
CommandListResources& res = m_command_lists[next_command_list];
WaitForFence(res.ready_fence_value, false);
m_current_command_list = next_command_list;
m_current_fence_value++;
res.ready_fence_value = m_current_fence_value;
res.init_command_list_used = false;
@@ -2179,13 +2181,13 @@ void GSDevice12::IASetIndexBuffer(const void* index, size_t count)
m_index_stream_buffer.CommitMemory(size);
}
void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor)
void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, bool depth_read)
{
GSTexture12* vkRt = static_cast<GSTexture12*>(rt);
GSTexture12* vkDs = static_cast<GSTexture12*>(ds);
pxAssert(vkRt || vkDs);
if (m_current_render_target != vkRt || m_current_depth_target != vkDs)
if (m_current_render_target != vkRt || m_current_depth_target != vkDs || m_current_depth_read_only != depth_read)
{
// framebuffer change
EndRenderPass();
@@ -2212,13 +2214,14 @@ void GSDevice12::OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector
m_current_render_target = vkRt;
m_current_depth_target = vkDs;
m_current_depth_read_only = depth_read;
if (!InRenderPass())
{
if (vkRt)
vkRt->TransitionToState(D3D12_RESOURCE_STATE_RENDER_TARGET);
if (vkDs)
vkDs->TransitionToState(D3D12_RESOURCE_STATE_DEPTH_WRITE);
vkDs->TransitionToState(depth_read ? (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE) : D3D12_RESOURCE_STATE_DEPTH_WRITE);
}
// This is used to set/initialize the framebuffer for tfx rendering.
@@ -3328,6 +3331,7 @@ void GSDevice12::UnbindTexture(GSTexture12* tex)
{
EndRenderPass();
m_current_depth_target = nullptr;
m_current_depth_read_only = false;
}
}
@@ -3448,7 +3452,7 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b
D3D12_RENDER_PASS_DEPTH_STENCIL_DESC ds = {};
if (m_current_depth_target)
{
ds.cpuDescriptor = m_current_depth_target->GetWriteDescriptor();
ds.cpuDescriptor = m_current_depth_read_only ? m_current_depth_target->GetReadDepthViewDescriptor() : m_current_depth_target->GetWriteDescriptor();
ds.DepthEndingAccess.Type = depth_end;
ds.DepthBeginningAccess.Type = depth_begin;
if (depth_begin == D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_CLEAR)
@@ -3468,7 +3472,8 @@ void GSDevice12::BeginRenderPass(D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE color_b
}
GetCommandList()->BeginRenderPass(m_current_render_target ? 1 : 0,
m_current_render_target ? &rt : nullptr, m_current_depth_target ? &ds : nullptr, D3D12_RENDER_PASS_FLAG_NONE);
m_current_render_target ? &rt : nullptr, m_current_depth_target ? &ds : nullptr,
(m_current_depth_target && m_current_depth_read_only) ? (D3D12_RENDER_PASS_FLAG_BIND_READ_ONLY_DEPTH) : D3D12_RENDER_PASS_FLAG_NONE);
}
void GSDevice12::EndRenderPass()
@@ -3550,11 +3555,13 @@ __ri void GSDevice12::ApplyBaseState(u32 flags, ID3D12GraphicsCommandList* cmdli
if (m_current_render_target)
{
cmdlist->OMSetRenderTargets(1, &m_current_render_target->GetWriteDescriptor().cpu_handle, FALSE,
m_current_depth_target ? &m_current_depth_target->GetWriteDescriptor().cpu_handle : nullptr);
m_current_depth_target ?
(m_current_depth_read_only ? &m_current_depth_target->GetReadDepthViewDescriptor().cpu_handle : &m_current_depth_target->GetWriteDescriptor().cpu_handle) :
nullptr);
}
else if (m_current_depth_target)
{
cmdlist->OMSetRenderTargets(0, nullptr, FALSE, &m_current_depth_target->GetWriteDescriptor().cpu_handle);
cmdlist->OMSetRenderTargets(0, nullptr, FALSE, m_current_depth_read_only ? &m_current_depth_target->GetReadDepthViewDescriptor().cpu_handle : &m_current_depth_target->GetWriteDescriptor().cpu_handle);
}
}
}
@@ -3845,6 +3852,7 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
GSTexture12* colclip_rt = static_cast<GSTexture12*>(g_gs_device->GetColorClipTexture());
GSTexture12* draw_rt = static_cast<GSTexture12*>(config.rt);
GSTexture12* draw_ds = static_cast<GSTexture12*>(config.ds);
GSTexture12* draw_rt_clone = nullptr;
// Align the render area to 128x128, hopefully avoiding render pass restarts for small render area changes (e.g. Ratchet and Clank).
const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize());
@@ -3854,6 +3862,18 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
// figure out the pipeline
UpdateHWPipelineSelector(config);
// Handle RT hazard when no barrier was requested
if (m_features.texture_barrier && config.tex && (config.tex == config.rt) && !(config.require_one_barrier || config.require_full_barrier))
{
g_perfmon.Put(GSPerfMon::Barriers, 1);
EndRenderPass();
// Specify null for the after resource as both resources are used after the barrier.
D3D12_RESOURCE_BARRIER barrier = {D3D12_RESOURCE_BARRIER_TYPE_ALIASING, D3D12_RESOURCE_BARRIER_FLAG_NONE};
barrier.Aliasing = {draw_rt->GetResource(), nullptr};
GetCommandList()->ResourceBarrier(1, &barrier);
}
// now blit the colclip texture back to the original target
if (colclip_rt)
{
@@ -3909,15 +3929,6 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
if (config.blend.constant_enable)
SetBlendConstants(config.blend.constant);
// Depth testing and sampling, bind resource as dsv read only and srv at the same time without the need of a copy.
if (config.tex && config.tex == config.ds)
{
EndRenderPass();
// Transition dsv as read only.
draw_ds->TransitionToState(D3D12_RESOURCE_STATE_DEPTH_READ);
}
// Primitive ID tracking DATE setup.
GSTexture12* date_image = nullptr;
if (config.destination_alpha == GSHWDrawConfig::DestinationAlphaMode::PrimIDTracking)
@@ -3999,8 +4010,28 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
}
const bool feedback = draw_rt && (config.require_one_barrier || (config.require_full_barrier && m_features.texture_barrier) || (config.tex && config.tex == config.rt));
if (feedback && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = static_cast<GSTexture12*>(CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true));
if (draw_rt_clone)
{
GL_PUSH("D3D12: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
EndRenderPass();
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone, true);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone, true);
}
else
Console.Warning("D3D12: Failed to allocate temp texture for RT copy.");
}
OMSetRenderTargets(draw_rt, draw_ds, config.scissor);
// For depth testing and sampling, use a read only dsv, otherwise use a write dsv
OMSetRenderTargets(draw_rt, draw_ds, config.scissor, config.tex && config.tex == config.ds);
// Begin render pass if new target or out of the area.
if (!m_in_render_pass)
@@ -4082,6 +4113,9 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
if (date_image)
Recycle(date_image);
if (draw_rt_clone)
Recycle(draw_rt_clone);
// now blit the colclip texture back to the original target
if (colclip_rt)
{
@@ -4118,6 +4152,12 @@ void GSDevice12::RenderHW(GSHWDrawConfig& config)
void GSDevice12::SendHWDraw(const PipelineSelector& pipe, const GSHWDrawConfig& config, GSTexture12* draw_rt, const bool feedback, const bool one_barrier, const bool full_barrier)
{
if (BindDrawPipeline(pipe) && !m_features.texture_barrier) [[unlikely]]
{
DrawIndexedPrimitive();
return;
}
if (feedback)
{
#ifdef PCSX2_DEVBUILD

View File

@@ -459,7 +459,7 @@ public:
void PSSetShaderResource(int i, GSTexture* sr, bool check_state, bool feedback = false);
void PSSetSampler(GSHWDrawConfig::SamplerSelector sel);
void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor);
void OMSetRenderTargets(GSTexture* rt, GSTexture* ds, const GSVector4i& scissor, bool depth_read = false);
void SetVSConstantBuffer(const GSHWDrawConfig::VSConstantBuffer& cb);
void SetPSConstantBuffer(const GSHWDrawConfig::PSConstantBuffer& cb);
@@ -580,6 +580,7 @@ private:
GSTexture12* m_current_render_target = nullptr;
GSTexture12* m_current_depth_target = nullptr;
bool m_current_depth_read_only = false;
D3D12_VIEWPORT m_viewport = {0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f};
GSVector4i m_scissor = GSVector4i::zero();

View File

@@ -17,13 +17,15 @@
GSTexture12::GSTexture12(Type type, Format format, int width, int height, int levels, DXGI_FORMAT dxgi_format,
wil::com_ptr_nothrow<ID3D12Resource> resource, wil::com_ptr_nothrow<ID3D12Resource> resource_fbl,
wil::com_ptr_nothrow<D3D12MA::Allocation> allocation, const D3D12DescriptorHandle& srv_descriptor,
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& uav_descriptor,
const D3D12DescriptorHandle& fbl_descriptor, WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state)
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& ro_dsv_descriptor,
const D3D12DescriptorHandle& uav_descriptor, const D3D12DescriptorHandle& fbl_descriptor,
WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state)
: m_resource(std::move(resource))
, m_resource_fbl(std::move(resource_fbl))
, m_allocation(std::move(allocation))
, m_srv_descriptor(srv_descriptor)
, m_write_descriptor(write_descriptor)
, m_read_dsv_descriptor(ro_dsv_descriptor)
, m_uav_descriptor(uav_descriptor)
, m_fbl_descriptor(fbl_descriptor)
, m_write_descriptor_type(wdtype)
@@ -58,6 +60,7 @@ void GSTexture12::Destroy(bool defer)
break;
case WriteDescriptorType::DSV:
dev->DeferDescriptorDestruction(dev->GetDSVHeapManager(), &m_write_descriptor);
dev->DeferDescriptorDestruction(dev->GetDSVHeapManager(), &m_read_dsv_descriptor);
break;
case WriteDescriptorType::None:
default:
@@ -87,6 +90,7 @@ void GSTexture12::Destroy(bool defer)
break;
case WriteDescriptorType::DSV:
dev->GetDSVHeapManager().Free(&m_write_descriptor);
dev->GetDSVHeapManager().Free(&m_read_dsv_descriptor);
break;
case WriteDescriptorType::None:
default:
@@ -237,7 +241,7 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
}
}
D3D12DescriptorHandle srv_descriptor, write_descriptor, uav_descriptor, fbl_descriptor;
D3D12DescriptorHandle srv_descriptor, write_descriptor, ro_dsv_descriptor, uav_descriptor, fbl_descriptor;
WriteDescriptorType write_descriptor_type = WriteDescriptorType::None;
if (srv_format != DXGI_FORMAT_UNKNOWN)
{
@@ -252,7 +256,7 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
write_descriptor_type = WriteDescriptorType::RTV;
if (!CreateRTVDescriptor(resource.get(), rtv_format, &write_descriptor))
{
dev->GetRTVHeapManager().Free(&srv_descriptor);
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
@@ -261,9 +265,15 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
case Type::DepthStencil:
{
write_descriptor_type = WriteDescriptorType::DSV;
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor))
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor, false))
{
dev->GetDSVHeapManager().Free(&srv_descriptor);
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
if (!CreateDSVDescriptor(resource.get(), dsv_format, &ro_dsv_descriptor, true))
{
dev->GetDSVHeapManager().Free(&write_descriptor);
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
@@ -275,7 +285,18 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
if (uav_format != DXGI_FORMAT_UNKNOWN && !CreateUAVDescriptor(resource.get(), dsv_format, &uav_descriptor))
{
dev->GetDescriptorHeapManager().Free(&write_descriptor);
switch (write_descriptor_type)
{
case WriteDescriptorType::RTV:
dev->GetRTVHeapManager().Free(&write_descriptor);
break;
case WriteDescriptorType::DSV:
dev->GetDSVHeapManager().Free(&ro_dsv_descriptor);
dev->GetDSVHeapManager().Free(&write_descriptor);
break;
default:
break;
}
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
@@ -285,7 +306,18 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
if (!CreateSRVDescriptor(resource_fbl.get(), levels, srv_format, &fbl_descriptor))
{
dev->GetDescriptorHeapManager().Free(&uav_descriptor);
dev->GetDescriptorHeapManager().Free(&write_descriptor);
switch (write_descriptor_type)
{
case WriteDescriptorType::RTV:
dev->GetRTVHeapManager().Free(&write_descriptor);
break;
case WriteDescriptorType::DSV:
dev->GetDSVHeapManager().Free(&ro_dsv_descriptor);
dev->GetDSVHeapManager().Free(&write_descriptor);
break;
default:
break;
}
dev->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
@@ -293,7 +325,7 @@ std::unique_ptr<GSTexture12> GSTexture12::Create(Type type, Format format, int w
return std::unique_ptr<GSTexture12>(
new GSTexture12(type, format, width, height, levels, dxgi_format, std::move(resource), std::move(resource_fbl), std::move(allocation),
srv_descriptor, write_descriptor, uav_descriptor, fbl_descriptor, write_descriptor_type, state));
srv_descriptor, write_descriptor, ro_dsv_descriptor, uav_descriptor, fbl_descriptor, write_descriptor_type, state));
}
std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resource> resource, Type type, Format format,
@@ -302,7 +334,7 @@ std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resou
{
const D3D12_RESOURCE_DESC desc = resource->GetDesc();
D3D12DescriptorHandle srv_descriptor, write_descriptor, uav_descriptor;
D3D12DescriptorHandle srv_descriptor, write_descriptor, ro_dsv_descriptor, uav_descriptor;
WriteDescriptorType write_descriptor_type = WriteDescriptorType::None;
if (srv_format != DXGI_FORMAT_UNKNOWN)
{
@@ -315,16 +347,22 @@ std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resou
write_descriptor_type = WriteDescriptorType::RTV;
if (!CreateRTVDescriptor(resource.get(), rtv_format, &write_descriptor))
{
GSDevice12::GetInstance()->GetRTVHeapManager().Free(&srv_descriptor);
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
else if (type == Type::DepthStencil)
{
write_descriptor_type = WriteDescriptorType::DSV;
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor))
if (!CreateDSVDescriptor(resource.get(), dsv_format, &write_descriptor, false))
{
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&srv_descriptor);
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
if (!CreateDSVDescriptor(resource.get(), dsv_format, &ro_dsv_descriptor, true))
{
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&write_descriptor);
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
@@ -333,14 +371,26 @@ std::unique_ptr<GSTexture12> GSTexture12::Adopt(wil::com_ptr_nothrow<ID3D12Resou
{
if (!CreateUAVDescriptor(resource.get(), srv_format, &uav_descriptor))
{
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&write_descriptor);
switch (write_descriptor_type)
{
case WriteDescriptorType::RTV:
GSDevice12::GetInstance()->GetRTVHeapManager().Free(&write_descriptor);
break;
case WriteDescriptorType::DSV:
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&ro_dsv_descriptor);
GSDevice12::GetInstance()->GetDSVHeapManager().Free(&write_descriptor);
break;
default:
break;
}
GSDevice12::GetInstance()->GetDescriptorHeapManager().Free(&srv_descriptor);
return {};
}
}
return std::unique_ptr<GSTexture12>(new GSTexture12(type, format, static_cast<u32>(desc.Width), desc.Height,
desc.MipLevels, desc.Format, std::move(resource), {}, {}, srv_descriptor, write_descriptor, uav_descriptor,
desc.MipLevels, desc.Format, std::move(resource), {}, {}, srv_descriptor, write_descriptor, {}, uav_descriptor,
{}, write_descriptor_type, resource_state));
}
@@ -365,7 +415,7 @@ bool GSTexture12::CreateRTVDescriptor(ID3D12Resource* resource, DXGI_FORMAT form
{
if (!GSDevice12::GetInstance()->GetRTVHeapManager().Allocate(dh))
{
Console.Error("Failed to allocate SRV descriptor");
Console.Error("Failed to allocate RTV descriptor");
return false;
}
@@ -374,15 +424,15 @@ bool GSTexture12::CreateRTVDescriptor(ID3D12Resource* resource, DXGI_FORMAT form
return true;
}
bool GSTexture12::CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh)
bool GSTexture12::CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh, bool read_only)
{
if (!GSDevice12::GetInstance()->GetDSVHeapManager().Allocate(dh))
{
Console.Error("Failed to allocate SRV descriptor");
Console.Error("Failed to allocate DSV descriptor");
return false;
}
const D3D12_DEPTH_STENCIL_VIEW_DESC desc = {format, D3D12_DSV_DIMENSION_TEXTURE2D, D3D12_DSV_FLAG_NONE};
const D3D12_DEPTH_STENCIL_VIEW_DESC desc = {format, D3D12_DSV_DIMENSION_TEXTURE2D, read_only ? D3D12_DSV_FLAG_READ_ONLY_DEPTH : D3D12_DSV_FLAG_NONE };
GSDevice12::GetInstance()->GetDevice()->CreateDepthStencilView(resource, &desc, dh->cpu_handle);
return true;
}
@@ -677,7 +727,36 @@ void GSTexture12::TransitionToState(ID3D12GraphicsCommandList* cmdlist, D3D12_RE
if (m_resource_state == state)
return;
TransitionSubresourceToState(cmdlist, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, m_resource_state, state);
// Read only depth requires special handling as we might want to write stencil.
// Also batch the transition barriers as per recommendation from docs.
if (state == (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
// Transition to read depth/write stencil
const D3D12_RESOURCE_BARRIER barriers[2] = {
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 0, m_resource_state, (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)}}},
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 1, m_resource_state, D3D12_RESOURCE_STATE_DEPTH_WRITE}}},
};
GSDevice12::GetInstance()->GetCommandList()->ResourceBarrier(m_resource_state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
}
else if (m_resource_state == (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE))
{
// Transition from read depth/write stencil
const D3D12_RESOURCE_BARRIER barriers[2] = {
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 0, (D3D12_RESOURCE_STATE_DEPTH_READ | D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE), state}}},
{D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
{{m_resource.get(), 1, D3D12_RESOURCE_STATE_DEPTH_WRITE, state}}},
};
GSDevice12::GetInstance()->GetCommandList()->ResourceBarrier(state == D3D12_RESOURCE_STATE_DEPTH_WRITE ? 1 : 2, barriers);
}
else
{
// Normal transition
TransitionSubresourceToState(cmdlist, D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, m_resource_state, state);
}
m_resource_state = state;
}

View File

@@ -30,6 +30,7 @@ public:
__fi const D3D12DescriptorHandle& GetSRVDescriptor() const { return m_srv_descriptor; }
__fi const D3D12DescriptorHandle& GetWriteDescriptor() const { return m_write_descriptor; }
__fi const D3D12DescriptorHandle& GetReadDepthViewDescriptor() const { return m_read_dsv_descriptor; }
__fi const D3D12DescriptorHandle& GetUAVDescriptor() const { return m_uav_descriptor; }
__fi const D3D12DescriptorHandle& GetFBLDescriptor() const { return m_fbl_descriptor; }
__fi D3D12_RESOURCE_STATES GetResourceState() const { return m_resource_state; }
@@ -72,13 +73,14 @@ private:
GSTexture12(Type type, Format format, int width, int height, int levels, DXGI_FORMAT dxgi_format,
wil::com_ptr_nothrow<ID3D12Resource> resource, wil::com_ptr_nothrow<ID3D12Resource> resource_fbl,
wil::com_ptr_nothrow<D3D12MA::Allocation> allocation, const D3D12DescriptorHandle& srv_descriptor,
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& uav_descriptor,
const D3D12DescriptorHandle& fbl_descriptor, WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state);
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& ro_dsv_descriptor,
const D3D12DescriptorHandle& uav_descriptor, const D3D12DescriptorHandle& fbl_descriptor,
WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state);
static bool CreateSRVDescriptor(
ID3D12Resource* resource, u32 levels, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
static bool CreateRTVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
static bool CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
static bool CreateDSVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh, bool read_only);
static bool CreateUAVDescriptor(ID3D12Resource* resource, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
ID3D12GraphicsCommandList* GetCommandBufferForUpdate();
@@ -91,6 +93,7 @@ private:
D3D12DescriptorHandle m_srv_descriptor = {};
D3D12DescriptorHandle m_write_descriptor = {};
D3D12DescriptorHandle m_read_dsv_descriptor = {};
D3D12DescriptorHandle m_uav_descriptor = {};
D3D12DescriptorHandle m_fbl_descriptor = {};
WriteDescriptorType m_write_descriptor_type = WriteDescriptorType::None;

View File

@@ -8037,6 +8037,14 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.drawarea = m_channel_shuffle ? scissor : scissor.rintersect(ComputeBoundingBox(rtsize, rtscale));
m_conf.scissor = (DATE && !DATE_BARRIER) ? m_conf.drawarea : scissor;
// ComputeDrawlistGetSize expects the original index layout, so needs to be called before we modify it via HandleProvokingVertexFirst/SetupIA.
if (m_conf.require_full_barrier && (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy))
{
ComputeDrawlistGetSize(rt->m_scale);
m_conf.drawlist = &m_drawlist;
m_conf.drawlist_bbox = &m_drawlist_bbox;
}
HandleProvokingVertexFirst();
SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0);
@@ -8125,13 +8133,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.alpha_second_pass.enable = false;
}
if (m_conf.require_full_barrier && (g_gs_device->Features().texture_barrier || g_gs_device->Features().multidraw_fb_copy))
{
ComputeDrawlistGetSize(rt->m_scale);
m_conf.drawlist = &m_drawlist;
m_conf.drawlist_bbox = &m_drawlist_bbox;
}
if (!m_channel_shuffle_width)
g_gs_device->RenderHW(m_conf);
else

View File

@@ -2430,6 +2430,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
GSVector2i rtsize = (config.rt ? config.rt : config.ds)->GetSize();
GSTexture* primid_texture = nullptr;
GSTexture* draw_rt_clone = nullptr;
GSTexture* colclip_rt = g_gs_device->GetColorClipTexture();
if (colclip_rt)
@@ -2505,23 +2506,6 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
break;
}
GSTexture* draw_rt_clone = nullptr;
if (config.require_one_barrier && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true);
if (draw_rt_clone)
{
GL_PUSH("GL: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(colclip_rt ? colclip_rt : config.rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
}
else
Console.Warning("GL: Failed to allocate temp texture for RT copy.");
}
IASetVertexBuffer(config.verts, config.nverts, GetVertexAlignment(config.vs.expand));
m_vertex.start *= GetExpansionFactor(config.vs.expand);
@@ -2550,9 +2534,7 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
PSSetShaderResource(0, config.tex);
if (config.pal)
PSSetShaderResource(1, config.pal);
if (draw_rt_clone)
PSSetShaderResource(2, draw_rt_clone);
else if (config.require_one_barrier || config.require_full_barrier)
if (m_features.texture_barrier && (config.require_one_barrier || config.require_full_barrier))
PSSetShaderResource(2, colclip_rt ? colclip_rt : config.rt);
SetupSampler(config.sampler);
@@ -2668,6 +2650,25 @@ void GSDeviceOGL::RenderHW(GSHWDrawConfig& config)
glTextureBarrier();
}
if (draw_rt && (config.require_one_barrier || (config.tex && config.tex == config.rt)) && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true);
if (draw_rt_clone)
{
GL_PUSH("GL: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone);
}
else
Console.Warning("GL: Failed to allocate temp texture for RT copy.");
}
OMSetRenderTargets(draw_rt, draw_ds, &config.scissor);
OMSetColorMaskState(config.colormask);
SetupOM(config.depth);

View File

@@ -5583,7 +5583,6 @@ GSTextureVK* GSDeviceVK::SetupPrimitiveTrackingDATE(GSHWDrawConfig& config)
void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
{
const GSVector2i rtsize(config.rt ? config.rt->GetSize() : config.ds->GetSize());
GSTextureVK* draw_rt = static_cast<GSTextureVK*>(config.rt);
GSTextureVK* draw_ds = static_cast<GSTextureVK*>(config.ds);
@@ -5716,24 +5715,6 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
break;
}
if (config.require_one_barrier && !m_features.texture_barrier)
{
// requires a copy of the RT
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, colclip_rt ? GSTexture::Format::ColorClip : GSTexture::Format::Color, true));
if (draw_rt_clone)
{
EndRenderPass();
GL_PUSH("VK: Copy RT to temp texture for fbmask {%d,%d %dx%d}", config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
PSSetShaderResource(2, draw_rt_clone, true);
}
else
Console.Warning("VK: Failed to allocate temp texture for RT copy.");
}
// Switch to colclip target for colclip hw rendering
if (pipe.ps.colclip_hw)
{
@@ -5811,6 +5792,26 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
pipe.feedback_loop_flags |= m_current_framebuffer_feedback_loop;
}
if (draw_rt && (config.require_one_barrier || (config.tex && config.tex == config.rt)) && !m_features.texture_barrier)
{
// Requires a copy of the RT.
draw_rt_clone = static_cast<GSTextureVK*>(CreateTexture(rtsize.x, rtsize.y, 1, draw_rt->GetFormat(), true));
if (draw_rt_clone)
{
GL_PUSH("VK: Copy RT to temp texture {%d,%d %dx%d}",
config.drawarea.left, config.drawarea.top,
config.drawarea.width(), config.drawarea.height());
EndRenderPass();
CopyRect(draw_rt, draw_rt_clone, config.drawarea, config.drawarea.left, config.drawarea.top);
if (config.require_one_barrier)
PSSetShaderResource(2, draw_rt_clone, true);
if (config.tex && config.tex == config.rt)
PSSetShaderResource(0, draw_rt_clone, true);
}
else
Console.Warning("VK: Failed to allocate temp texture for RT copy.");
}
// We don't need the very first barrier if this is the first draw after switching to feedback loop,
// because the layout change in itself enforces the execution dependency. colclip hw needs a barrier between
// setup and the first draw to read it. TODO: Make colclip hw use subpasses instead.