Compare commits

..

4 Commits

Author SHA1 Message Date
Mac 2f4b8a335d shader: allow SDWA byte destinations for bitwise VOP2 ops 2026-10-08 22:12:05 +04:00
nmzik b38b7454a6 Preserve vertex export locations for mixed pixel interpolation 2026-10-08 22:05:53 +04:00
nmzik b65dc12208 Track pad port ownership and reject duplicate opens 2026-10-08 21:58:53 +04:00
Devran Cosmo Uenal 58978d154a font: rasterize glyphs larger than 128 pixels (#1214)
Glyph images were rendered into a fixed 128x128 buffer and their width and
height were clamped to 128. Since glyphs are rasterized at the requested
scale (f96a14a0), large text such as title cards lost every row and column
past 128 pixels, so letters were drawn cut into bands.

The glyph buffers now grow to the glyph's bitmap box and width_byte is the
glyph's width. The VGA fallback font keeps its 128-pixel limit; scalable
glyphs are limited to 4096 pixels only to reject absurd scales.
2026-10-08 20:03:13 +04:00
13 changed files with 373 additions and 103 deletions
@@ -617,6 +617,13 @@ PipelineCache::GraphicsPrograms PipelineCache::GetGraphicsPrograms(
ShaderParams pixel_params;
if (pixel_active) {
pixel_params = PrepareProgram(pixel_regs, sh, target_export_mapping, pixel_info);
if (!mesh_active && !tess_active && Prospero::IsRectList(user_config.GetPrimType())) {
pixel_info.parameter_mode = ShaderPixelParameterMode::Rectangle;
} else if (context.GetModeControl().provoking_vtx_last) {
pixel_info.parameter_mode = ShaderPixelParameterMode::LastVertex;
} else {
pixel_info.parameter_mode = ShaderPixelParameterMode::FirstVertex;
}
const auto& blend = context.GetBlendControl(0);
pixel_info.dual_source_blending =
blend.enable && !context.GetRenderTarget(0).info.blend_bypass &&
@@ -177,6 +177,13 @@ uint32_t EmitAttribute(EmitterState& state, uint32_t attr, uint32_t chan) {
return value;
};
if (input->per_vertex) {
if (PixelParameterIsFlat(state, attr)) {
const auto value = load_per_vertex(
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u);
const auto bits = state.builder.AllocateId();
state.builder.AddFunction(spv::OpBitcast, TypeU32(state), bits, value);
return bits;
}
const auto barycentric_kind = state.input_info.pixel->ps_no_perspective
? IR::StageInputKind::BaryCoordNoPerspective
: IR::StageInputKind::BaryCoordSmooth;
@@ -231,8 +238,12 @@ uint32_t EmitInterpolationParameter(ValueEmitContext& ctx, uint32_t attr, uint32
return value;
};
const auto selected_vertex = (mode + 1u) % 3u;
uint32_t value = load_vertex(selected_vertex);
uint32_t selected_vertex = (mode + 1u) % 3u;
if (mode == 2u && PixelParameterIsFlat(state, attr)) {
selected_vertex =
state.input_info.pixel->parameter_mode == ShaderPixelParameterMode::LastVertex ? 2u : 0u;
}
uint32_t value = load_vertex(selected_vertex);
if (!PixelParameterIsCustom(state, attr) && mode < 2u) {
const auto delta = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFSub, TypeF32(state), delta, value, load_vertex(0));
@@ -997,7 +997,8 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
// Bitwise: WriteRawU32 inserts a byte or word destination like any other partial write.
{SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
};
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
static_cast<size_t>(Vop2SdwaProfile::Count));
@@ -227,15 +227,16 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
}
}
}
// Aliases of a vertex output share one SPIR-V interface variable. If any
// alias reads raw vertices, interpolate the other aliases from those too.
// Mixed interpolation modes share raw vertices at their guest export slot.
// Rectangle expansion alone supplies separate flat and smooth outputs.
for (uint32_t input = 0; input < pixel->input_num; input++) {
for (uint32_t alias = 0; alias < pixel->input_num; alias++) {
const bool same_mode = ShaderPixelParameterIsFlat(*pixel, input) ==
ShaderPixelParameterIsFlat(*pixel, alias);
if (ShaderPixelParameterMappedLocation(*pixel, input) ==
ShaderPixelParameterMappedLocation(*pixel, alias) &&
ShaderPixelParameterIsFlat(*pixel, input) ==
ShaderPixelParameterIsFlat(*pixel, alias)) {
per_vertex[input] = per_vertex[input] || per_vertex[alias];
(pixel->parameter_mode != ShaderPixelParameterMode::Rectangle || same_mode)) {
per_vertex[input] = per_vertex[input] || per_vertex[alias] || !same_mode;
}
}
}
@@ -244,7 +245,8 @@ void CollectPixelInputs(const Program& program, const ShaderPixelInputInfo* pixe
per_vertex[input]);
}
for (uint32_t input = 0; input < pixel->input_num; input++) {
if (interpolated[input] && per_vertex[input]) {
if (interpolated[input] && per_vertex[input] &&
!ShaderPixelParameterIsFlat(*pixel, input)) {
const auto kind = pixel->ps_no_perspective ? StageInputKind::BaryCoordNoPerspective
: StageInputKind::BaryCoordSmooth;
AddInput(info, kind, 0, 3,
+1
View File
@@ -663,6 +663,7 @@ void BuildStageStaticKey(const ShaderPixelInputInfo& info, std::vector<uint32_t>
key.push_back(static_cast<uint32_t>(info.ps_front_face));
key.push_back(static_cast<uint32_t>(info.ps_ancillary));
key.push_back(static_cast<uint32_t>(info.ps_no_perspective));
key.push_back(static_cast<uint32_t>(info.parameter_mode));
key.push_back(static_cast<uint32_t>(info.ps_pixel_kill_enable));
key.push_back(static_cast<uint32_t>(info.ps_depth_export_enable));
key.push_back(static_cast<uint32_t>(info.ps_sample_mask_export_enable));
+2
View File
@@ -159,6 +159,7 @@ struct ShaderComputeInputInfo: ShaderWorkgroupInputInfo {
};
enum class ShaderAlphaBlendSource : uint8_t { None, SourceAlpha, SourceAlphaOne, SourceAlphaZero };
enum class ShaderPixelParameterMode : uint8_t { FirstVertex, LastVertex, Rectangle };
struct ShaderPixelInputInfo {
uint32_t interpolator_settings[32] = {0};
@@ -180,6 +181,7 @@ struct ShaderPixelInputInfo {
bool ps_front_face = false;
bool ps_ancillary = false;
bool ps_no_perspective = false;
ShaderPixelParameterMode parameter_mode = ShaderPixelParameterMode::FirstVertex;
bool ps_pixel_kill_enable = false;
bool ps_depth_export_enable = false;
bool ps_sample_mask_export_enable = false;
+4 -2
View File
@@ -30,6 +30,9 @@ uint32_t ShaderPixelParameterMappedLocation(const ShaderPixelInputInfo& info, ui
uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
std::span<const uint32_t> active_inputs, uint32_t input) {
if (info.parameter_mode != ShaderPixelParameterMode::Rectangle) {
return ShaderPixelParameterMappedLocation(info, input);
}
std::array<bool, 32> used_locations {};
for (const auto active_input: active_inputs) {
used_locations[ShaderPixelParameterMappedLocation(info, active_input)] = true;
@@ -42,8 +45,7 @@ uint32_t ShaderPixelParameterLocation(const ShaderPixelInputInfo& info,
auto& location = group_locations[group];
if (location == UINT32_MAX) {
location = mapped;
// Smooth and custom interpolation read the same vertex output. Only
// differing flat/smooth rectangle outputs need separate locations.
// Rectangle expansion broadcasts flat aliases through separate outputs.
if (group_locations[group ^ 1u] != UINT32_MAX) {
location = 0;
while (location < used_locations.size() && used_locations[location]) {
+112 -57
View File
@@ -24,8 +24,12 @@ namespace Libs::Controller {
LIB_NAME("Pad", "Pad");
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
constexpr int PAD_ERROR_INVALID_ARG = -2137915391; /* 0x80920001 */
constexpr int PAD_ERROR_INVALID_HANDLE = -2137915389; /* 0x80920003 */
constexpr int PAD_ERROR_ALREADY_OPENED = -2137915388; /* 0x80920004 */
constexpr int PAD_ERROR_NOT_INITIALIZED = -2137915387; /* 0x80920005 */
constexpr int PAD_ERROR_NO_HANDLE = -2137915384; /* 0x80920008 */
constexpr int PAD_HANDLE = 1;
constexpr uint32_t RUMBLE_DURATION_MS = 0xffff;
constexpr uint32_t RELEASE_FLUSH_MS = 50;
@@ -107,6 +111,12 @@ public:
KYTY_CLASS_NO_COPY(GameController);
int InitPort();
int OpenPort(int user_id, int type, int index);
int GetPortHandle(int user_id, int type, int index);
int ClosePort(int handle);
int CheckPortHandle(int handle);
void Connect(int id);
void Disconnect(int id);
void Button(int id, uint32_t button, bool down);
@@ -131,6 +141,9 @@ public:
private:
static constexpr uint32_t STATES_MAX = 64;
enum class PortState { Uninitialized, Closed, Open };
int CheckPortHandleLocked(int handle) const;
void CheckActive();
void AddState();
@@ -140,6 +153,7 @@ private:
void UpdateTriggerEffectState();
Common::Mutex m_mutex;
PortState m_port_state = PortState::Uninitialized;
std::vector<int> m_connected_ids;
int m_active_id = -1;
bool m_connected = false;
@@ -925,21 +939,71 @@ int GetActiveControllerId() {
return g_controller != nullptr ? g_controller->GetActiveControllerId() : -1;
}
int KYTY_SYSV_ABI PadInit() {
PRINT_NAME();
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
// The host keyboard and gamepad feed one standard port for the configured user.
return user_id == Config::GetUserId() && type == 0 && index == 0;
}
int GameController::InitPort() {
Common::LockGuard lock(m_mutex);
if (m_port_state == PortState::Uninitialized) {
m_port_state = PortState::Closed;
}
return OK;
}
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
constexpr int user_id_system = 0xff;
constexpr int port_type_standard = 0;
constexpr int port_type_special = 2;
constexpr int port_type_remote = 16;
const bool personal_port =
user_id == Config::GetUserId() && (type == port_type_standard || type == port_type_special);
const bool system_remote_control = user_id == user_id_system && type == port_type_remote;
return index == 0 && (personal_port || system_remote_control);
int GameController::OpenPort(int user_id, int type, int index) {
Common::LockGuard lock(m_mutex);
if (m_port_state == PortState::Uninitialized) {
return PAD_ERROR_NOT_INITIALIZED;
}
if (!PadOpenArgsAreValid(user_id, type, index)) {
return PAD_ERROR_INVALID_ARG;
}
if (m_port_state == PortState::Open) {
return PAD_ERROR_ALREADY_OPENED;
}
m_port_state = PortState::Open;
return PAD_HANDLE;
}
int GameController::GetPortHandle(int user_id, int type, int index) {
Common::LockGuard lock(m_mutex);
if (m_port_state == PortState::Uninitialized) {
return PAD_ERROR_NOT_INITIALIZED;
}
return m_port_state == PortState::Open && PadOpenArgsAreValid(user_id, type, index) ? PAD_HANDLE
: PAD_ERROR_NO_HANDLE;
}
int GameController::CheckPortHandleLocked(int handle) const {
if (m_port_state == PortState::Uninitialized) {
return PAD_ERROR_NOT_INITIALIZED;
}
return m_port_state == PortState::Open && handle == PAD_HANDLE ? OK : PAD_ERROR_INVALID_HANDLE;
}
int GameController::CheckPortHandle(int handle) {
Common::LockGuard lock(m_mutex);
return CheckPortHandleLocked(handle);
}
int GameController::ClosePort(int handle) {
Common::LockGuard lock(m_mutex);
if (const auto error = CheckPortHandleLocked(handle); error != OK) {
return error;
}
m_port_state = PortState::Closed;
return OK;
}
int PadCheckHandle(int handle) {
return g_controller->CheckPortHandle(handle);
}
int KYTY_SYSV_ABI PadInit() {
PRINT_NAME();
return g_controller->InitPort();
}
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
@@ -951,15 +1015,7 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
"\t param = 0x%016" PRIx64 "\n",
user_id, type, index, reinterpret_cast<uint64_t>(param));
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
if (!PadOpenArgsAreValid(user_id, type, index)) {
return pad_error_invalid_arg;
}
int handle = 1;
return handle;
return g_controller->OpenPort(user_id, type, index);
}
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
@@ -970,20 +1026,20 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
"\t index = %d\n",
user_id, type, index);
constexpr int pad_error_device_no_handle = -2137915384; /* 0x80920008 */
return g_controller->GetPortHandle(user_id, type, index);
}
if (!PadOpenArgsAreValid(user_id, type, index)) {
return pad_error_device_no_handle;
}
return 1;
int KYTY_SYSV_ABI PadClose(int handle) {
PRINT_NAME();
LOGF("\t handle = %d\n", handle);
return g_controller->ClosePort(handle);
}
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
@@ -995,8 +1051,8 @@ int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable) {
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
LOGF("\t enable = %s\n", (enable ? "true" : "false"));
@@ -1007,8 +1063,8 @@ int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable) {
int KYTY_SYSV_ABI PadResetOrientation(int handle) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
g_controller->ResetOrientation();
@@ -1018,18 +1074,17 @@ int KYTY_SYSV_ABI PadResetOrientation(int handle) {
int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformation* info) {
PRINT_NAME();
int connected_count = 0;
bool connected = false;
g_controller->GetConnectionInfo(&connected, &connected_count);
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (info == nullptr) {
return PAD_ERROR_INVALID_ARG;
}
int connected_count = 0;
bool connected = false;
g_controller->GetConnectionInfo(&connected, &connected_count);
std::memset(info, 0, sizeof(*info));
info->touch_pixel_density = 44.86f;
@@ -1048,8 +1103,8 @@ int KYTY_SYSV_ABI PadGetControllerInformation(int handle, PadControllerInformati
int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (is_remote == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1062,8 +1117,8 @@ int KYTY_SYSV_ABI PadIsRemoteController(int handle, bool* is_remote) {
int KYTY_SYSV_ABI PadReadState(int handle, PadData* data) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (data == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1084,8 +1139,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
PRINT_NAME();
EXIT_NOT_IMPLEMENTED(num < 1 || num > 64);
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (data == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1116,8 +1171,8 @@ int KYTY_SYSV_ABI PadRead(int handle, PadData* data, int num) {
int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (param == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1135,8 +1190,8 @@ int KYTY_SYSV_ABI PadSetVibration(int handle, const PadVibrationParam* param) {
int KYTY_SYSV_ABI PadResetLightBar(int handle) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
return OK;
@@ -1145,8 +1200,8 @@ int KYTY_SYSV_ABI PadResetLightBar(int handle) {
int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (param == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1160,8 +1215,8 @@ int KYTY_SYSV_ABI PadSetLightBar(int handle, const PadLightBarParam* param) {
int KYTY_SYSV_ABI PadSetTriggerEffect(int handle, const PadTriggerEffectParam* param) {
PRINT_NAME();
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (param == nullptr) {
return PAD_ERROR_INVALID_ARG;
@@ -1175,8 +1230,8 @@ int KYTY_SYSV_ABI PadGetTriggerEffectState(int handle, PadTriggerEffectStateInfo
LOGF("\t handle = %d\n", handle);
if (handle != 1) {
return PAD_ERROR_INVALID_HANDLE;
if (const auto error = PadCheckHandle(handle); error != OK) {
return error;
}
if (info == nullptr) {
return PAD_ERROR_INVALID_ARG;
+3
View File
@@ -81,9 +81,12 @@ enum class Setting { SpeakerVolume, VibrationIntensity, TriggerEffectIntensity }
void CycleSetting(Setting setting);
float GetSettingScale(Setting setting);
int PadCheckHandle(int handle);
int KYTY_SYSV_ABI PadInit();
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index);
int KYTY_SYSV_ABI PadClose(int handle);
int KYTY_SYSV_ABI PadSetMotionSensorState(int handle, bool enable);
int KYTY_SYSV_ABI PadSetAngularVelocityDeadbandState(int handle, bool enable);
int KYTY_SYSV_ABI PadResetOrientation(int handle);
+17 -15
View File
@@ -78,6 +78,8 @@ constexpr int SCE_FONT_TEXT_PARSER_RESULT_TERMINATE = 0;
constexpr int SCE_FONT_TEXT_PARSER_RESULT_ERROR = -1;
constexpr int SCE_FONT_WRITING_FORM_HORIZONTAL = 0x10;
constexpr int FONT_BITMAP_MAX_DIM = 128;
// TrueType glyphs are rasterized at the requested size; the limit only rejects absurd scales.
constexpr int FONT_GLYPH_MAX_DIM = 4096;
constexpr int FONT_ERROR_INVALID_PARAMETER = static_cast<int>(0x80460002u);
constexpr int FONT_ERROR_INVALID_FONT_HANDLE = static_cast<int>(0x80460005u);
constexpr int FONT_ERROR_NO_SUPPORT_CODE = static_cast<int>(0x80460041u);
@@ -206,7 +208,7 @@ struct FontState {
const uint8_t* ttf_data;
uint32_t ttf_size;
bool has_ttf;
std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM> fallback_image;
std::vector<uint8_t> fallback_image;
FontTransImage trans_image;
};
@@ -227,7 +229,7 @@ struct GlyphState {
uint32_t code;
int attribute;
FontGlyphMetrics metrics;
std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM> image;
std::vector<uint8_t> image;
FontTransImage trans_image;
};
@@ -851,8 +853,8 @@ static bool get_stb_metrics(FontState* font, uint32_t code, FontGlyphMetrics* me
return true;
}
static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM>* image,
FontTransImage* trans_image, uint32_t code, FontState* font) {
static bool init_stb_image(std::vector<uint8_t>* image, FontTransImage* trans_image, uint32_t code,
FontState* font) {
if (image == nullptr || trans_image == nullptr || !ensure_stb_font(font)) {
return false;
}
@@ -868,12 +870,12 @@ static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP
stbtt_GetCodepointBitmapBox(&font->font_info, static_cast<int>(code), scale_x, scale_y, &x0, &y0,
&x1, &y1);
image->fill(0);
const auto width = static_cast<uint32_t>(std::clamp(x1 - x0, 0, FONT_BITMAP_MAX_DIM));
const auto height = static_cast<uint32_t>(std::clamp(y1 - y0, 0, FONT_BITMAP_MAX_DIM));
const auto width = static_cast<uint32_t>(std::clamp(x1 - x0, 0, FONT_GLYPH_MAX_DIM));
const auto height = static_cast<uint32_t>(std::clamp(y1 - y0, 0, FONT_GLYPH_MAX_DIM));
// Keep at least one byte so that blank glyphs still get a valid address.
image->assign(std::max<size_t>(static_cast<size_t>(width) * height, 1), 0);
trans_image->address = image->data();
trans_image->width_byte = FONT_BITMAP_MAX_DIM;
trans_image->width_byte = width;
trans_image->image_width = width;
trans_image->image_height = height;
@@ -882,7 +884,7 @@ static bool init_stb_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP
}
stbtt_MakeCodepointBitmap(&font->font_info, image->data(), static_cast<int>(width),
static_cast<int>(height), FONT_BITMAP_MAX_DIM, scale_x, scale_y,
static_cast<int>(height), static_cast<int>(width), scale_x, scale_y,
static_cast<int>(code));
return true;
@@ -939,8 +941,8 @@ static uint32_t normalize_bitmap_font_code(uint32_t code) {
return static_cast<uint32_t>('?');
}
static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX_DIM>* image,
FontTransImage* trans_image, uint32_t code, const FontState* font) {
static void init_image(std::vector<uint8_t>* image, FontTransImage* trans_image, uint32_t code,
const FontState* font) {
EXIT_NOT_IMPLEMENTED(image == nullptr);
EXIT_NOT_IMPLEMENTED(trans_image == nullptr);
@@ -948,11 +950,11 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
return;
}
image->fill(0);
code = normalize_bitmap_font_code(code);
const auto width = static_cast<uint32_t>(scaled_font_width(font));
const auto height = static_cast<uint32_t>(scaled_font_height(font));
image->assign(static_cast<size_t>(width) * height, 0);
#if LIBAVUTIL_VERSION_MAJOR < 61
const auto* vga_font = avpriv_vga16_font;
#else
@@ -965,13 +967,13 @@ static void init_image(std::array<uint8_t, FONT_BITMAP_MAX_DIM * FONT_BITMAP_MAX
for (uint32_t x = 0; x < width; x++) {
const uint32_t src_x = std::min<uint32_t>((x * 8u) / width, 7u);
if ((row & (0x80u >> src_x)) != 0) {
(*image)[y * FONT_BITMAP_MAX_DIM + x] = 0xff;
(*image)[y * width + x] = 0xff;
}
}
}
trans_image->address = image->data();
trans_image->width_byte = FONT_BITMAP_MAX_DIM;
trans_image->width_byte = width;
trans_image->image_width = width;
trans_image->image_height = height;
}
+10 -20
View File
@@ -19,7 +19,7 @@ static int KYTY_SYSV_ABI PadSetVibrationMode(int handle, int mode) {
"\t mode = %d\n",
handle, mode);
return 0;
return Controller::PadCheckHandle(handle);
}
static int KYTY_SYSV_ABI PadSetVibrationTriggerEffectWeakWhileEmbeddedMicInUse(bool enabled) {
@@ -66,11 +66,10 @@ static int KYTY_SYSV_ABI
PadDeviceClassGetExtendedInformation(int handle, PadDeviceClassExtendedInformation* info) {
PRINT_NAME();
constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
if (handle != 1) {
return pad_error_invalid_handle;
if (const auto error = Controller::PadCheckHandle(handle); error != OK) {
return error;
}
if (info == nullptr) {
return pad_error_invalid_arg;
@@ -85,16 +84,15 @@ static int KYTY_SYSV_ABI PadDeviceClassParseData(int handle, const Controller::P
PadDeviceClassData* class_data) {
PRINT_NAME();
constexpr int pad_error_invalid_handle = -2137915389; /* 0x80920003 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
LOGF("\t handle = %d\n"
"\t data = 0x%016" PRIx64 "\n"
"\t class_data = 0x%016" PRIx64 "\n",
handle, reinterpret_cast<uint64_t>(data), reinterpret_cast<uint64_t>(class_data));
if (handle != 1) {
return pad_error_invalid_handle;
if (const auto error = Controller::PadCheckHandle(handle); error != OK) {
return error;
}
if (data == nullptr || class_data == nullptr) {
return pad_error_invalid_arg;
@@ -122,15 +120,7 @@ static int KYTY_SYSV_ABI PadSetTiltCorrectionState(int handle, bool enabled) {
"\t enabled = %d\n",
handle, enabled ? 1 : 0);
return 0;
}
static int KYTY_SYSV_ABI PadClose(int handle) {
PRINT_NAME();
LOGF("\t handle = %d\n", handle);
return OK;
return Controller::PadCheckHandle(handle);
}
// PPSA02385
@@ -154,7 +144,7 @@ static int KYTY_SYSV_ABI PadGetExtControllerInformationStub(int handle, void* in
"\t info = 0x%016" PRIx64 "\n",
handle, reinterpret_cast<uint64_t>(info));
return OK;
return Controller::PadCheckHandle(handle);
}
static int KYTY_SYSV_ABI PadUnknownN3kSX62fgNo(uint64_t arg0, uint64_t arg1, uint64_t arg2,
@@ -197,7 +187,7 @@ LIB_DEFINE(InitPad_1) {
LIB_FUNC("znaWI0gpuo8", Controller::PadGetTriggerEffectState);
LIB_FUNC("DscD1i9HX1w", Controller::PadResetLightBar);
LIB_FUNC("RR4novUEENY", Controller::PadSetLightBar);
LIB_FUNC("6ncge5+l5Qs", PadClose);
LIB_FUNC("6ncge5+l5Qs", Controller::PadClose);
LIB_FUNC("n3kSX62fgNo", PadUnknownN3kSX62fgNo);
}
+51
View File
@@ -110,6 +110,8 @@ struct Controller {
now = 1000;
haptics_handles_rumble = false;
Initialize();
Check(PadInit() == 0 && PadOpen(Config::GetUserId(), 0, 0, nullptr) == 1,
"the standard controller port did not open");
Connect(1);
Check(GetSettingScale(Setting::SpeakerVolume) ==
Config::GetControllerSpeakerVolume() / 50.0f &&
@@ -143,6 +145,54 @@ void SetRumble(uint8_t large, uint8_t small) {
Check(PadSetVibration(1, &param) == 0, "vibration request failed");
}
void TestPadPortOwnership() {
Initialize();
const int user = Config::GetUserId();
constexpr auto not_initialized = static_cast<int32_t>(0x80920005u);
constexpr auto already_opened = static_cast<int32_t>(0x80920004u);
constexpr auto invalid_handle = static_cast<int32_t>(0x80920003u);
constexpr auto no_handle = static_cast<int32_t>(0x80920008u);
PadData data {};
Check(PadOpen(user, 0, 0, nullptr) == not_initialized &&
PadGetHandle(user, 0, 0) == not_initialized &&
PadReadState(1, &data) == not_initialized,
"an uninitialized library exposed a controller port");
Check(PadInit() == 0 && PadGetHandle(user, 0, 0) == no_handle,
"initializing the library opened a controller port");
const int handle = PadOpen(user, 0, 0, nullptr);
Check(handle == 1 && PadInit() == 0 && PadGetHandle(user, 0, 0) == handle,
"reinitializing the library lost the open port");
for (int duplicate = 0; duplicate < 4; duplicate++) {
const int rejected = PadOpen(user, 0, 0, nullptr);
Check(rejected == already_opened,
"opening one pad five times exposed duplicate connected controllers");
Check(PadClose(rejected) == invalid_handle && PadGetHandle(user, 0, 0) == handle,
"closing a rejected duplicate released the original owner");
}
Check(PadGetHandle(user + 1, 0, 0) == no_handle &&
PadGetHandle(user, 2, 0) == no_handle && PadGetHandle(user, 0, 1) == no_handle,
"a different port tuple aliased the open standard pad");
SetButton(HOST_INPUT_CONTROLLER_ID, PAD_BUTTON_CROSS, true);
Check(PadReadState(handle, &data) == 0 && data.connected &&
data.buttons == PAD_BUTTON_CROSS,
"keyboard Cross did not reach the owned port");
SetButton(HOST_INPUT_CONTROLLER_ID, PAD_BUTTON_CROSS, false);
Connect(1);
SetButton(1, PAD_BUTTON_CROSS, true);
Check(PadReadState(handle, &data) == 0 && data.buttons == PAD_BUTTON_CROSS,
"the physical controller did not share the owned port");
Disconnect(1);
Check(PadGetHandle(user, 0, 0) == handle && PadOpen(user, 0, 0, nullptr) == already_opened,
"host disconnection released guest port ownership");
Check(PadClose(handle) == 0 && PadGetHandle(user, 0, 0) == no_handle &&
PadReadState(handle, &data) == invalid_handle &&
PadRead(handle, &data, 1) == invalid_handle && PadClose(handle) == invalid_handle,
"a closed port retained a usable handle");
Check(PadOpen(user, 0, 0, nullptr) == handle && PadReadState(handle, &data) == 0,
"a closed port could not reopen");
Shutdown();
}
void TestSettingCycles() {
Controller controller;
for (auto setting:
@@ -503,6 +553,7 @@ void TestTriggerEffectState() {
int main() {
Config::Initialize();
TestPadPortOwnership();
TestSettingCycles();
TestGlobalControllerLevels();
// Each following test initializes another controller and requires strong defaults.
+143
View File
@@ -1353,6 +1353,7 @@ struct GraphicsCase {
std::vector<u32> push_constants;
std::vector<u32> pixel_interpolator_settings;
bool pixel_no_perspective = false;
bool provoking_vtx_last = false;
std::vector<u32> vertices;
bool pixel_ancillary = false;
bool pixel_front_face = false;
@@ -1449,6 +1450,20 @@ void CheckPixelParameterAliases() {
pixel.interpolator_settings[1] = 0u;
pixel.interpolator_settings[2] = 1u;
const std::array<uint32_t, 3> active = {0, 1, 2};
Require(name, "ordinary mixed aliases",
ShaderPixelParameterLocation(pixel, active, 0) == 0u &&
ShaderPixelParameterLocation(pixel, active, 1) == 0u &&
ShaderPixelParameterLocation(pixel, active, 2) == 1u,
"flat/smooth aliases must retain the real vertex export location");
std::vector<uint32_t> ordinary_key, rectangle_key, provoking_key;
BuildStageStaticKey(pixel, ordinary_key);
pixel.parameter_mode = ShaderPixelParameterMode::LastVertex;
BuildStageStaticKey(pixel, provoking_key);
pixel.parameter_mode = ShaderPixelParameterMode::Rectangle;
BuildStageStaticKey(pixel, rectangle_key);
Require(name, "interpolation specialization",
ordinary_key != rectangle_key && ordinary_key != provoking_key,
"rectangle and provoking-vertex modes must participate in the shader key");
Require(name, "reserved physical locations",
ShaderPixelParameterLocation(pixel, active, 0) == 0u &&
ShaderPixelParameterLocation(pixel, active, 1) == 2u &&
@@ -1473,6 +1488,7 @@ void CheckRectListShaders() {
vertex.stage.program = &vertex_program;
vertex.stage.resources = &empty_snapshot;
ShaderPixelInputInfo pixel{};
pixel.parameter_mode = ShaderPixelParameterMode::Rectangle;
pixel.input_num = 2;
pixel.interpolator_settings[0] = 0x400u;
pixel.interpolator_settings[1] = 0;
@@ -1774,6 +1790,8 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) {
? 1u
: static_cast<u32>(test.pixel_interpolator_settings.size());
pixel_info.ps_no_perspective = test.pixel_no_perspective;
pixel_info.parameter_mode = test.provoking_vtx_last ? ShaderPixelParameterMode::LastVertex
: ShaderPixelParameterMode::FirstVertex;
pixel_info.ps_ancillary = test.pixel_ancillary;
pixel_info.ps_front_face = test.pixel_front_face;
pixel_info.ps_pos_w = test.pixel_position_w;
@@ -16928,6 +16946,12 @@ public:
raster.frontFace = vk::FrontFace::eCounterClockwise;
raster.lineWidth = 1.0f;
vk::PipelineRasterizationProvokingVertexStateCreateInfoEXT provoking_vertex{};
if (test.provoking_vtx_last) {
provoking_vertex.provokingVertexMode = vk::ProvokingVertexModeEXT::eLastVertex;
raster.pNext = &provoking_vertex;
}
vk::PipelineMultisampleStateCreateInfo multisample{};
multisample.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo;
multisample.rasterizationSamples = test.samples;
@@ -22118,6 +22142,75 @@ TestCase Vop1SdwaNotPartialSourcesAndDestinations() {
return test;
}
TestCase Vop2SdwaBitwiseByteDestinations(u32 wave_size) {
using O = ShaderOpcode;
// Low result bytes of AND, OR, XOR, XNOR for the two runtime inputs.
constexpr std::array<u32, 4> result_bytes{0xa4u, 0xbeu, 0x1au, 0xe5u};
constexpr std::array<u32, 4> alias_expected{
0xc350e5a6u, 0xc3fce5a6u, 0xc3ace5a6u, 0xc353e5a6u};
constexpr u32 sentinel = 0xa1b2c3d4u;
TestCase test;
test.name = wave_size == 64 ? "Vop2SdwaBitwiseByteDestinationsWave64"
: "Vop2SdwaBitwiseByteDestinationsWave32";
test.initial = {0xc3d4e5a6u, 0x9b785abcu};
test.expected = test.initial;
auto &code = test.code;
const auto store = [&](u32 reg, u32 expected) {
AppendStoreVgpr(&code, reg, static_cast<u32>(test.expected.size()));
test.expected.push_back(expected);
};
for (u32 op = 0; op < 4; ++op) {
AppendVMovU32(&code, 30, 0);
AppendBufferLoadDword(&code, 12, 30);
AppendVMovU32(&code, 30, 4);
AppendBufferLoadDword(&code, 13, 30);
for (u32 selector = 0; selector < 4; ++selector) {
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 26, sentinel);
code.push_back(EncodeVop2(0x1b + op, 26, 249, 13));
code.push_back(EncodeVop2Sdwa(12, selector, unused));
// RDNA2 table 88: pad, sign extend above/zero below, or preserve.
const u32 shift = selector * 8u;
u32 expected = result_bytes[op] << shift;
if (unused == 1 && (result_bytes[op] & 0x80u) != 0) {
expected = (0xffffff00u | result_bytes[op]) << shift;
} else if (unused == 2) {
expected |= sentinel & ~(0xffu << shift);
}
store(26, expected);
}
}
// Read source byte2 and word1 before overwriting source0's byte2.
code.push_back(EncodeVop2(0x1b + op, 12, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 2, 2, 2, 5));
store(12, alias_expected[op]);
// Source1 may also alias the destination.
AppendVMovU32(&code, 30, 0);
AppendBufferLoadDword(&code, 12, 30);
code.push_back(EncodeVop2(0x1b + op, 13, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 1, 2));
store(13, 0x9b7800bcu | (result_bytes[op] << 8u));
for (u32 unused = 0; unused < 3; ++unused) {
AppendVMovLiteral(&code, 26, sentinel);
code.push_back(EncodeSMovB32(126, InlineU32(0)));
code.push_back(EncodeVop2(0x1b + op, 26, 249, 13));
code.push_back(EncodeVop2Sdwa(12, 2, unused));
code.push_back(EncodeSMovB32(126, InlineU32(1)));
store(26, sentinel);
}
}
AppendEnd(&code);
test.initial.resize(test.expected.size());
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD,
O::V_AND_B32, O::V_OR_B32, O::V_XOR_B32, O::V_XNOR_B32,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.required_spirv = {"OpBitwiseAnd", "OpBitwiseOr", "OpBitwiseXor", "OpNot",
"OpBitFieldInsert", "OpBitFieldSExtract"};
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase Vop1SdwaMovByteDestinations() {
using O = ShaderOpcode;
@@ -35871,6 +35964,38 @@ GraphicsCase GraphicsSmoothRawInputAlias() {
return test;
}
GraphicsCase GraphicsSmoothFlatInputAlias(bool flat_first, bool provoking_last = false) {
auto test = GraphicsSmoothRawInputAlias();
test.name = provoking_last
? (flat_first ? "GraphicsFlatSmoothAliasLast" : "GraphicsSmoothFlatAliasLast")
: (flat_first ? "GraphicsFlatSmoothInputAlias" : "GraphicsSmoothFlatInputAlias");
test.pixel_custom_interpolation_mask = 0;
test.provoking_vtx_last = provoking_last;
test.pixel_interpolator_settings = flat_first ? std::vector<u32>{0x400u, 0u}
: std::vector<u32>{0u, 0x400u};
const u32 smooth = flat_first ? 1u : 0u;
const u32 flat = smooth ^ 1u;
// One export carries smooth values (2,4,8) and packed light IDs (5,7,11).
// Flat P0 preserves the ID bits; flat P1/P2 uses the same provoking vertex.
test.vertices[4] = 0x3f800005u;
test.vertices[10] = 0x3f800007u;
test.vertices[16] = 0x3f80000bu;
test.fragment_code = {EncodeVintrp(0x00, 0, smooth, 1, 0),
EncodeVintrp(0x01, 0, smooth, 1, 1),
EncodeVintrp(0x02, 1, flat, 2, 2),
EncodeVop2(0x1b, 1, InlineU32(63), 1),
EncodeVop1(0x06, 1, Vgpr(1)),
EncodeVintrp(0x00, 2, flat, 1, 0),
EncodeVintrp(0x01, 2, flat, 1, 1),
EncodeExp0(0x00, 0xf), EncodeExp1(0, 1, 2, 0)};
AppendEnd(&test.fragment_code);
test.expected_pixel = {0x40700000u, provoking_last ? 0x41300000u : 0x40a00000u,
provoking_last ? 0x41000000u : 0x40000000u, 0x40700000u};
test.opcodes.insert(test.opcodes.end(),
{ShaderOpcode::V_AND_B32, ShaderOpcode::V_CVT_F32_U32});
return test;
}
GraphicsCase GraphicsAncillaryLayer(bool front_face) {
GraphicsCase test;
test.name = front_face ? "GraphicsAncillaryAfterFrontFace" : "GraphicsAncillaryLayer";
@@ -36278,6 +36403,8 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop1SdwaNotCapturedByte0Source);
AddCase(Vop1SdwaNotPreservesHighWordDestination);
AddCase(Vop1SdwaNotPartialSourcesAndDestinations);
cases.push_back(Vop2SdwaBitwiseByteDestinations(32));
cases.push_back(Vop2SdwaBitwiseByteDestinations(64));
AddCase(Vop1SdwaMovByteDestinations);
cases.push_back(Vop1SdwaMovByteSources(32));
cases.push_back(Vop1SdwaMovByteSources(64));
@@ -36714,6 +36841,10 @@ std::vector<GraphicsCase> MakeGraphicsCases() {
GraphicsPackedHalfInputAlias(false),
GraphicsPackedHalfInputAlias(true),
GraphicsSmoothRawInputAlias(),
GraphicsSmoothFlatInputAlias(false),
GraphicsSmoothFlatInputAlias(true),
GraphicsSmoothFlatInputAlias(false, true),
GraphicsSmoothFlatInputAlias(true, true),
GraphicsAncillaryLayer(false),
GraphicsAncillaryLayer(true),
GraphicsAncillarySampleId(),
@@ -42196,6 +42327,11 @@ int main(int argc, char **argv) {
RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(false));
RunGraphicsCase(&vulkan, GraphicsPackedHalfInputAlias(true));
RunGraphicsCase(&vulkan, GraphicsSmoothRawInputAlias());
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(false));
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(true));
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(false, true));
RunGraphicsCase(&vulkan, GraphicsSmoothFlatInputAlias(true, true));
RunGraphicsCase(&vulkan, GraphicsFlatInterpolatorExport());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--clip-control-only") == 0) {
@@ -42350,6 +42486,13 @@ int main(int argc, char **argv) {
RunCase(&vulkan, ScalarZeroShiftWithRuntimeCount());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-bitwise-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop2SdwaBitwiseByteDestinations(32));
RunCase(&vulkan, Vop2SdwaBitwiseByteDestinations(64));
RunCase(&vulkan, Vop1SdwaNotPartialSourcesAndDestinations());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--sdwa-mov-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop1SdwaMovByteDestinations());