Rebase to master, fix merge conflicts
This commit is contained in:
commit
4489ea6f53
|
@ -1,4 +1,13 @@
|
||||||
<!--
|
---
|
||||||
|
name: Bug Report / Feature Request
|
||||||
|
about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better.
|
||||||
|
title: ''
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!---
|
||||||
Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
|
Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
|
||||||
|
|
||||||
Please read the FAQ:
|
Please read the FAQ:
|
|
@ -0,0 +1,8 @@
|
||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: yuzu Discord
|
||||||
|
url: https://discord.com/invite/u77vRWY
|
||||||
|
about: If you are experiencing an issue with yuzu, and you need tech support, or if you have a general question, try asking in the official yuzu Discord linked here. Piracy is not allowed.
|
||||||
|
- name: Community forums
|
||||||
|
url: https://community.citra-emu.org
|
||||||
|
about: This is an alternative place for tech support, however helpers there are not as active.
|
|
@ -331,8 +331,10 @@ endif()
|
||||||
|
|
||||||
# Ensure libusb is properly configured (based on dolphin libusb include)
|
# Ensure libusb is properly configured (based on dolphin libusb include)
|
||||||
find_package(LibUSB)
|
find_package(LibUSB)
|
||||||
|
if (NOT LIBUSB_FOUND)
|
||||||
add_subdirectory(externals/libusb)
|
add_subdirectory(externals/libusb)
|
||||||
set(LIBUSB_LIBRARIES usb)
|
set(LIBUSB_LIBRARIES usb)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
# Prefer the -pthread flag on Linux.
|
# Prefer the -pthread flag on Linux.
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 9250d5ae8f50202005233dc0512a1d460c8b4833
|
Subproject commit 8188e3fbbc105591064093440f88081fb957d4f0
|
|
@ -0,0 +1,43 @@
|
||||||
|
# - Find libusb-1.0 library
|
||||||
|
# This module defines
|
||||||
|
# LIBUSB_INCLUDE_DIR, where to find bluetooth.h
|
||||||
|
# LIBUSB_LIBRARIES, the libraries needed to use libusb-1.0.
|
||||||
|
# LIBUSB_FOUND, If false, do not try to use libusb-1.0.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2009, Michal Cihar, <michal@cihar.com>
|
||||||
|
#
|
||||||
|
# vim: expandtab sw=4 ts=4 sts=4:
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found")
|
||||||
|
message(STATUS "libusb-1.0 not found.")
|
||||||
|
elseif (NOT LIBUSB_FOUND)
|
||||||
|
pkg_check_modules (LIBUSB_PKG libusb-1.0)
|
||||||
|
|
||||||
|
find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h
|
||||||
|
PATHS
|
||||||
|
${LIBUSB_PKG_INCLUDE_DIRS}
|
||||||
|
/usr/include/libusb-1.0
|
||||||
|
/usr/include
|
||||||
|
/usr/local/include/libusb-1.0
|
||||||
|
/usr/local/include
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(LIBUSB_LIBRARIES NAMES usb-1.0 usb
|
||||||
|
PATHS
|
||||||
|
${LIBUSB_PKG_LIBRARY_DIRS}
|
||||||
|
/usr/lib
|
||||||
|
/usr/local/lib
|
||||||
|
)
|
||||||
|
|
||||||
|
if(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
set(LIBUSB_FOUND TRUE CACHE INTERNAL "libusb-1.0 found")
|
||||||
|
message(STATUS "Found libusb-1.0: ${LIBUSB_INCLUDE_DIR}, ${LIBUSB_LIBRARIES}")
|
||||||
|
else(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found")
|
||||||
|
message(STATUS "libusb-1.0 not found.")
|
||||||
|
endif(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||||
|
|
||||||
|
mark_as_advanced(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES)
|
||||||
|
endif ()
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/arm/cpu_interrupt_handler.h"
|
#include "core/arm/cpu_interrupt_handler.h"
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/file_sys/control_metadata.h"
|
#include "core/file_sys/control_metadata.h"
|
||||||
#include "core/file_sys/patch_manager.h"
|
#include "core/file_sys/patch_manager.h"
|
||||||
|
#include "core/file_sys/registered_cache.h"
|
||||||
#include "core/file_sys/savedata_factory.h"
|
#include "core/file_sys/savedata_factory.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
@ -1353,14 +1354,25 @@ void IApplicationFunctions::GetDisplayVersion(Kernel::HLERequestContext& ctx) {
|
||||||
|
|
||||||
std::array<u8, 0x10> version_string{};
|
std::array<u8, 0x10> version_string{};
|
||||||
|
|
||||||
FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
|
const auto res = [this] {
|
||||||
const auto res = pm.GetControlMetadata();
|
const auto title_id = system.CurrentProcess()->GetTitleID();
|
||||||
|
|
||||||
|
FileSys::PatchManager pm{title_id};
|
||||||
|
auto res = pm.GetControlMetadata();
|
||||||
|
if (res.first != nullptr) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSys::PatchManager pm_update{FileSys::GetUpdateTitleID(title_id)};
|
||||||
|
return pm_update.GetControlMetadata();
|
||||||
|
}();
|
||||||
|
|
||||||
if (res.first != nullptr) {
|
if (res.first != nullptr) {
|
||||||
const auto& version = res.first->GetVersionString();
|
const auto& version = res.first->GetVersionString();
|
||||||
std::copy(version.begin(), version.end(), version_string.begin());
|
std::copy(version.begin(), version.end(), version_string.begin());
|
||||||
} else {
|
} else {
|
||||||
constexpr u128 default_version = {1, 0};
|
constexpr char default_version[]{"1.0.0"};
|
||||||
std::memcpy(version_string.data(), default_version.data(), sizeof(u128));
|
std::memcpy(version_string.data(), default_version, sizeof(default_version));
|
||||||
}
|
}
|
||||||
|
|
||||||
IPC::ResponseBuilder rb{ctx, 6};
|
IPC::ResponseBuilder rb{ctx, 6};
|
||||||
|
|
|
@ -548,9 +548,9 @@ struct Memory::Impl {
|
||||||
// longer exist, and we should just leave the pagetable entry blank.
|
// longer exist, and we should just leave the pagetable entry blank.
|
||||||
page_type = Common::PageType::Unmapped;
|
page_type = Common::PageType::Unmapped;
|
||||||
} else {
|
} else {
|
||||||
page_type = Common::PageType::Memory;
|
|
||||||
current_page_table->pointers[vaddr >> PAGE_BITS] =
|
current_page_table->pointers[vaddr >> PAGE_BITS] =
|
||||||
pointer - (vaddr & ~PAGE_MASK);
|
pointer - (vaddr & ~PAGE_MASK);
|
||||||
|
page_type = Common::PageType::Memory;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -591,9 +591,12 @@ struct Memory::Impl {
|
||||||
base + page_table.pointers.size());
|
base + page_table.pointers.size());
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
|
ASSERT_MSG(type != Common::PageType::Memory,
|
||||||
|
"Mapping memory page without a pointer @ {:016x}", base * PAGE_SIZE);
|
||||||
|
|
||||||
while (base != end) {
|
while (base != end) {
|
||||||
page_table.pointers[base] = nullptr;
|
|
||||||
page_table.attributes[base] = type;
|
page_table.attributes[base] = type;
|
||||||
|
page_table.pointers[base] = nullptr;
|
||||||
page_table.backing_addr[base] = 0;
|
page_table.backing_addr[base] = 0;
|
||||||
|
|
||||||
base += 1;
|
base += 1;
|
||||||
|
|
|
@ -24,10 +24,14 @@ Adapter::Adapter() {
|
||||||
LOG_INFO(Input, "GC Adapter Initialization started");
|
LOG_INFO(Input, "GC Adapter Initialization started");
|
||||||
|
|
||||||
current_status = NO_ADAPTER_DETECTED;
|
current_status = NO_ADAPTER_DETECTED;
|
||||||
libusb_init(&libusb_ctx);
|
|
||||||
get_origin.fill(true);
|
get_origin.fill(true);
|
||||||
|
|
||||||
|
const int init_res = libusb_init(&libusb_ctx);
|
||||||
|
if (init_res == LIBUSB_SUCCESS) {
|
||||||
StartScanThread();
|
StartScanThread();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) {
|
GCPadStatus Adapter::GetPadStatus(int port, const std::array<u8, 37>& adapter_payload) {
|
||||||
|
@ -218,11 +222,18 @@ void Adapter::Setup() {
|
||||||
adapter_controllers_status.fill(ControllerTypes::None);
|
adapter_controllers_status.fill(ControllerTypes::None);
|
||||||
|
|
||||||
// pointer to list of connected usb devices
|
// pointer to list of connected usb devices
|
||||||
libusb_device** devices;
|
libusb_device** devices{};
|
||||||
|
|
||||||
// populate the list of devices, get the count
|
// populate the list of devices, get the count
|
||||||
const std::size_t device_count = libusb_get_device_list(libusb_ctx, &devices);
|
const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices);
|
||||||
|
if (device_count < 0) {
|
||||||
|
LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count);
|
||||||
|
detect_thread_running = false; // Stop the loop constantly checking for gc adapter
|
||||||
|
// TODO: For hotplug+gc adapter checkbox implementation, revert this.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (devices != nullptr) {
|
||||||
for (std::size_t index = 0; index < device_count; ++index) {
|
for (std::size_t index = 0; index < device_count; ++index) {
|
||||||
if (CheckDeviceAccess(devices[index])) {
|
if (CheckDeviceAccess(devices[index])) {
|
||||||
// GC Adapter found and accessible, registering it
|
// GC Adapter found and accessible, registering it
|
||||||
|
@ -230,6 +241,8 @@ void Adapter::Setup() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
libusb_free_device_list(devices, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Adapter::CheckDeviceAccess(libusb_device* device) {
|
bool Adapter::CheckDeviceAccess(libusb_device* device) {
|
||||||
|
@ -286,7 +299,13 @@ bool Adapter::CheckDeviceAccess(libusb_device* device) {
|
||||||
|
|
||||||
void Adapter::GetGCEndpoint(libusb_device* device) {
|
void Adapter::GetGCEndpoint(libusb_device* device) {
|
||||||
libusb_config_descriptor* config = nullptr;
|
libusb_config_descriptor* config = nullptr;
|
||||||
libusb_get_config_descriptor(device, 0, &config);
|
const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config);
|
||||||
|
if (config_descriptor_return != LIBUSB_SUCCESS) {
|
||||||
|
LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}",
|
||||||
|
config_descriptor_return);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
|
for (u8 ic = 0; ic < config->bNumInterfaces; ic++) {
|
||||||
const libusb_interface* interfaceContainer = &config->interface[ic];
|
const libusb_interface* interfaceContainer = &config->interface[ic];
|
||||||
for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
|
for (int i = 0; i < interfaceContainer->num_altsetting; i++) {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
#include <libusb.h>
|
#include <libusb.h>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/threadsafe_queue.h"
|
#include "common/threadsafe_queue.h"
|
||||||
|
|
|
@ -39,52 +39,18 @@ constexpr std::array POLYGON_OFFSET_ENABLE_LUT = {
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
void FixedPipelineState::DepthStencil::Fill(const Maxwell& regs) noexcept {
|
void FixedPipelineState::Fill(const Maxwell& regs, bool has_extended_dynamic_state) {
|
||||||
raw = 0;
|
|
||||||
front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
|
|
||||||
front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
|
|
||||||
front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
|
|
||||||
front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
|
|
||||||
if (regs.stencil_two_side_enable) {
|
|
||||||
back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
|
|
||||||
back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
|
|
||||||
back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
|
|
||||||
back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
|
|
||||||
} else {
|
|
||||||
back.action_stencil_fail.Assign(front.action_stencil_fail);
|
|
||||||
back.action_depth_fail.Assign(front.action_depth_fail);
|
|
||||||
back.action_depth_pass.Assign(front.action_depth_pass);
|
|
||||||
back.test_func.Assign(front.test_func);
|
|
||||||
}
|
|
||||||
depth_test_enable.Assign(regs.depth_test_enable);
|
|
||||||
depth_write_enable.Assign(regs.depth_write_enabled);
|
|
||||||
depth_bounds_enable.Assign(regs.depth_bounds_enable);
|
|
||||||
stencil_enable.Assign(regs.stencil_enable);
|
|
||||||
depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
|
|
||||||
}
|
|
||||||
|
|
||||||
void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept {
|
|
||||||
const auto& clip = regs.view_volume_clip_control;
|
const auto& clip = regs.view_volume_clip_control;
|
||||||
const std::array enabled_lut = {regs.polygon_offset_point_enable,
|
const std::array enabled_lut = {regs.polygon_offset_point_enable,
|
||||||
regs.polygon_offset_line_enable,
|
regs.polygon_offset_line_enable,
|
||||||
regs.polygon_offset_fill_enable};
|
regs.polygon_offset_fill_enable};
|
||||||
const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
|
const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
|
||||||
|
|
||||||
u32 packed_front_face = PackFrontFace(regs.front_face);
|
|
||||||
if (regs.screen_y_control.triangle_rast_flip != 0) {
|
|
||||||
// Flip front face
|
|
||||||
packed_front_face = 1 - packed_front_face;
|
|
||||||
}
|
|
||||||
|
|
||||||
raw = 0;
|
raw = 0;
|
||||||
topology.Assign(topology_index);
|
|
||||||
primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
|
primitive_restart_enable.Assign(regs.primitive_restart.enabled != 0 ? 1 : 0);
|
||||||
cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
|
|
||||||
depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
|
depth_bias_enable.Assign(enabled_lut[POLYGON_OFFSET_ENABLE_LUT[topology_index]] != 0 ? 1 : 0);
|
||||||
depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value());
|
depth_clamp_disabled.Assign(regs.view_volume_clip_control.depth_clamp_disabled.Value());
|
||||||
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
|
ndc_minus_one_to_one.Assign(regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1 : 0);
|
||||||
cull_face.Assign(PackCullFace(regs.cull_face));
|
|
||||||
front_face.Assign(packed_front_face);
|
|
||||||
polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
|
polygon_mode.Assign(PackPolygonMode(regs.polygon_mode_front));
|
||||||
patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
|
patch_control_points_minus_one.Assign(regs.patch_vertices - 1);
|
||||||
tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value()));
|
tessellation_primitive.Assign(static_cast<u32>(regs.tess_mode.prim.Value()));
|
||||||
|
@ -93,19 +59,37 @@ void FixedPipelineState::Rasterizer::Fill(const Maxwell& regs) noexcept {
|
||||||
logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
|
logic_op_enable.Assign(regs.logic_op.enable != 0 ? 1 : 0);
|
||||||
logic_op.Assign(PackLogicOp(regs.logic_op.operation));
|
logic_op.Assign(PackLogicOp(regs.logic_op.operation));
|
||||||
rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
|
rasterize_enable.Assign(regs.rasterize_enable != 0 ? 1 : 0);
|
||||||
|
|
||||||
std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast
|
std::memcpy(&point_size, ®s.point_size, sizeof(point_size)); // TODO: C++20 std::bit_cast
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||||
|
binding_divisors[index] =
|
||||||
|
regs.instanced_arrays.IsInstancingEnabled(index) ? regs.vertex_array[index].divisor : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
||||||
|
const auto& input = regs.vertex_attrib_format[index];
|
||||||
|
auto& attribute = attributes[index];
|
||||||
|
attribute.raw = 0;
|
||||||
|
attribute.enabled.Assign(input.IsConstant() ? 0 : 1);
|
||||||
|
attribute.buffer.Assign(input.buffer);
|
||||||
|
attribute.offset.Assign(input.offset);
|
||||||
|
attribute.type.Assign(static_cast<u32>(input.type.Value()));
|
||||||
|
attribute.size.Assign(static_cast<u32>(input.size.Value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedPipelineState::ColorBlending::Fill(const Maxwell& regs) noexcept {
|
|
||||||
for (std::size_t index = 0; index < std::size(attachments); ++index) {
|
for (std::size_t index = 0; index < std::size(attachments); ++index) {
|
||||||
attachments[index].Fill(regs, index);
|
attachments[index].Fill(regs, index);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void FixedPipelineState::ViewportSwizzles::Fill(const Maxwell& regs) noexcept {
|
|
||||||
const auto& transform = regs.viewport_transform;
|
const auto& transform = regs.viewport_transform;
|
||||||
std::transform(transform.begin(), transform.end(), swizzles.begin(),
|
std::transform(transform.begin(), transform.end(), viewport_swizzles.begin(),
|
||||||
[](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
|
[](const auto& viewport) { return static_cast<u16>(viewport.swizzle.raw); });
|
||||||
|
|
||||||
|
if (!has_extended_dynamic_state) {
|
||||||
|
no_extended_dynamic_state.Assign(1);
|
||||||
|
dynamic_state.Fill(regs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) {
|
void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size_t index) {
|
||||||
|
@ -147,20 +131,57 @@ void FixedPipelineState::BlendingAttachment::Fill(const Maxwell& regs, std::size
|
||||||
enable.Assign(1);
|
enable.Assign(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FixedPipelineState::Fill(const Maxwell& regs) {
|
void FixedPipelineState::DynamicState::Fill(const Maxwell& regs) {
|
||||||
rasterizer.Fill(regs);
|
const u32 topology_index = static_cast<u32>(regs.draw.topology.Value());
|
||||||
depth_stencil.Fill(regs);
|
u32 packed_front_face = PackFrontFace(regs.front_face);
|
||||||
color_blending.Fill(regs);
|
if (regs.screen_y_control.triangle_rast_flip != 0) {
|
||||||
viewport_swizzles.Fill(regs);
|
// Flip front face
|
||||||
|
packed_front_face = 1 - packed_front_face;
|
||||||
|
}
|
||||||
|
|
||||||
|
raw1 = 0;
|
||||||
|
raw2 = 0;
|
||||||
|
front.action_stencil_fail.Assign(PackStencilOp(regs.stencil_front_op_fail));
|
||||||
|
front.action_depth_fail.Assign(PackStencilOp(regs.stencil_front_op_zfail));
|
||||||
|
front.action_depth_pass.Assign(PackStencilOp(regs.stencil_front_op_zpass));
|
||||||
|
front.test_func.Assign(PackComparisonOp(regs.stencil_front_func_func));
|
||||||
|
if (regs.stencil_two_side_enable) {
|
||||||
|
back.action_stencil_fail.Assign(PackStencilOp(regs.stencil_back_op_fail));
|
||||||
|
back.action_depth_fail.Assign(PackStencilOp(regs.stencil_back_op_zfail));
|
||||||
|
back.action_depth_pass.Assign(PackStencilOp(regs.stencil_back_op_zpass));
|
||||||
|
back.test_func.Assign(PackComparisonOp(regs.stencil_back_func_func));
|
||||||
|
} else {
|
||||||
|
back.action_stencil_fail.Assign(front.action_stencil_fail);
|
||||||
|
back.action_depth_fail.Assign(front.action_depth_fail);
|
||||||
|
back.action_depth_pass.Assign(front.action_depth_pass);
|
||||||
|
back.test_func.Assign(front.test_func);
|
||||||
|
}
|
||||||
|
stencil_enable.Assign(regs.stencil_enable);
|
||||||
|
depth_write_enable.Assign(regs.depth_write_enabled);
|
||||||
|
depth_bounds_enable.Assign(regs.depth_bounds_enable);
|
||||||
|
depth_test_enable.Assign(regs.depth_test_enable);
|
||||||
|
front_face.Assign(packed_front_face);
|
||||||
|
depth_test_func.Assign(PackComparisonOp(regs.depth_test_func));
|
||||||
|
topology.Assign(topology_index);
|
||||||
|
cull_face.Assign(PackCullFace(regs.cull_face));
|
||||||
|
cull_enable.Assign(regs.cull_test_enabled != 0 ? 1 : 0);
|
||||||
|
|
||||||
|
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||||
|
const auto& input = regs.vertex_array[index];
|
||||||
|
VertexBinding& binding = vertex_bindings[index];
|
||||||
|
binding.raw = 0;
|
||||||
|
binding.enabled.Assign(input.IsEnabled() ? 1 : 0);
|
||||||
|
binding.stride.Assign(static_cast<u16>(input.stride.Value()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t FixedPipelineState::Hash() const noexcept {
|
std::size_t FixedPipelineState::Hash() const noexcept {
|
||||||
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
|
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
|
||||||
return static_cast<std::size_t>(hash);
|
return static_cast<std::size_t>(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
|
bool FixedPipelineState::operator==(const FixedPipelineState& rhs) const noexcept {
|
||||||
return std::memcmp(this, &rhs, sizeof *this) == 0;
|
return std::memcmp(this, &rhs, Size()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept {
|
u32 FixedPipelineState::PackComparisonOp(Maxwell::ComparisonOp op) noexcept {
|
||||||
|
|
|
@ -60,14 +60,6 @@ struct FixedPipelineState {
|
||||||
|
|
||||||
void Fill(const Maxwell& regs, std::size_t index);
|
void Fill(const Maxwell& regs, std::size_t index);
|
||||||
|
|
||||||
std::size_t Hash() const noexcept;
|
|
||||||
|
|
||||||
bool operator==(const BlendingAttachment& rhs) const noexcept;
|
|
||||||
|
|
||||||
bool operator!=(const BlendingAttachment& rhs) const noexcept {
|
|
||||||
return !operator==(rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr std::array<bool, 4> Mask() const noexcept {
|
constexpr std::array<bool, 4> Mask() const noexcept {
|
||||||
return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
|
return {mask_r != 0, mask_g != 0, mask_b != 0, mask_a != 0};
|
||||||
}
|
}
|
||||||
|
@ -97,14 +89,7 @@ struct FixedPipelineState {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VertexInput {
|
union VertexAttribute {
|
||||||
union Binding {
|
|
||||||
u16 raw;
|
|
||||||
BitField<0, 1, u16> enabled;
|
|
||||||
BitField<1, 12, u16> stride;
|
|
||||||
};
|
|
||||||
|
|
||||||
union Attribute {
|
|
||||||
u32 raw;
|
u32 raw;
|
||||||
BitField<0, 1, u32> enabled;
|
BitField<0, 1, u32> enabled;
|
||||||
BitField<1, 5, u32> buffer;
|
BitField<1, 5, u32> buffer;
|
||||||
|
@ -121,71 +106,6 @@ struct FixedPipelineState {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<Binding, Maxwell::NumVertexArrays> bindings;
|
|
||||||
std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
|
|
||||||
std::array<Attribute, Maxwell::NumVertexAttributes> attributes;
|
|
||||||
|
|
||||||
void SetBinding(std::size_t index, bool enabled, u32 stride, u32 divisor) noexcept {
|
|
||||||
auto& binding = bindings[index];
|
|
||||||
binding.raw = 0;
|
|
||||||
binding.enabled.Assign(enabled ? 1 : 0);
|
|
||||||
binding.stride.Assign(static_cast<u16>(stride));
|
|
||||||
binding_divisors[index] = divisor;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetAttribute(std::size_t index, bool enabled, u32 buffer, u32 offset,
|
|
||||||
Maxwell::VertexAttribute::Type type,
|
|
||||||
Maxwell::VertexAttribute::Size size) noexcept {
|
|
||||||
auto& attribute = attributes[index];
|
|
||||||
attribute.raw = 0;
|
|
||||||
attribute.enabled.Assign(enabled ? 1 : 0);
|
|
||||||
attribute.buffer.Assign(buffer);
|
|
||||||
attribute.offset.Assign(offset);
|
|
||||||
attribute.type.Assign(static_cast<u32>(type));
|
|
||||||
attribute.size.Assign(static_cast<u32>(size));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Rasterizer {
|
|
||||||
union {
|
|
||||||
u32 raw;
|
|
||||||
BitField<0, 4, u32> topology;
|
|
||||||
BitField<4, 1, u32> primitive_restart_enable;
|
|
||||||
BitField<5, 1, u32> cull_enable;
|
|
||||||
BitField<6, 1, u32> depth_bias_enable;
|
|
||||||
BitField<7, 1, u32> depth_clamp_disabled;
|
|
||||||
BitField<8, 1, u32> ndc_minus_one_to_one;
|
|
||||||
BitField<9, 2, u32> cull_face;
|
|
||||||
BitField<11, 1, u32> front_face;
|
|
||||||
BitField<12, 2, u32> polygon_mode;
|
|
||||||
BitField<14, 5, u32> patch_control_points_minus_one;
|
|
||||||
BitField<19, 2, u32> tessellation_primitive;
|
|
||||||
BitField<21, 2, u32> tessellation_spacing;
|
|
||||||
BitField<23, 1, u32> tessellation_clockwise;
|
|
||||||
BitField<24, 1, u32> logic_op_enable;
|
|
||||||
BitField<25, 4, u32> logic_op;
|
|
||||||
BitField<29, 1, u32> rasterize_enable;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(Rodrigo): Move this to push constants
|
|
||||||
u32 point_size;
|
|
||||||
|
|
||||||
void Fill(const Maxwell& regs) noexcept;
|
|
||||||
|
|
||||||
constexpr Maxwell::PrimitiveTopology Topology() const noexcept {
|
|
||||||
return static_cast<Maxwell::PrimitiveTopology>(topology.Value());
|
|
||||||
}
|
|
||||||
|
|
||||||
Maxwell::CullFace CullFace() const noexcept {
|
|
||||||
return UnpackCullFace(cull_face.Value());
|
|
||||||
}
|
|
||||||
|
|
||||||
Maxwell::FrontFace FrontFace() const noexcept {
|
|
||||||
return UnpackFrontFace(front_face.Value());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DepthStencil {
|
|
||||||
template <std::size_t Position>
|
template <std::size_t Position>
|
||||||
union StencilFace {
|
union StencilFace {
|
||||||
BitField<Position + 0, 3, u32> action_stencil_fail;
|
BitField<Position + 0, 3, u32> action_stencil_fail;
|
||||||
|
@ -210,43 +130,75 @@ struct FixedPipelineState {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
union {
|
union VertexBinding {
|
||||||
u32 raw;
|
u16 raw;
|
||||||
StencilFace<0> front;
|
BitField<0, 12, u16> stride;
|
||||||
StencilFace<12> back;
|
BitField<12, 1, u16> enabled;
|
||||||
BitField<24, 1, u32> depth_test_enable;
|
|
||||||
BitField<25, 1, u32> depth_write_enable;
|
|
||||||
BitField<26, 1, u32> depth_bounds_enable;
|
|
||||||
BitField<27, 1, u32> stencil_enable;
|
|
||||||
BitField<28, 3, u32> depth_test_func;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void Fill(const Maxwell& regs) noexcept;
|
struct DynamicState {
|
||||||
|
union {
|
||||||
|
u32 raw1;
|
||||||
|
StencilFace<0> front;
|
||||||
|
StencilFace<12> back;
|
||||||
|
BitField<24, 1, u32> stencil_enable;
|
||||||
|
BitField<25, 1, u32> depth_write_enable;
|
||||||
|
BitField<26, 1, u32> depth_bounds_enable;
|
||||||
|
BitField<27, 1, u32> depth_test_enable;
|
||||||
|
BitField<28, 1, u32> front_face;
|
||||||
|
BitField<29, 3, u32> depth_test_func;
|
||||||
|
};
|
||||||
|
union {
|
||||||
|
u32 raw2;
|
||||||
|
BitField<0, 4, u32> topology;
|
||||||
|
BitField<4, 2, u32> cull_face;
|
||||||
|
BitField<6, 1, u32> cull_enable;
|
||||||
|
};
|
||||||
|
std::array<VertexBinding, Maxwell::NumVertexArrays> vertex_bindings;
|
||||||
|
|
||||||
|
void Fill(const Maxwell& regs);
|
||||||
|
|
||||||
Maxwell::ComparisonOp DepthTestFunc() const noexcept {
|
Maxwell::ComparisonOp DepthTestFunc() const noexcept {
|
||||||
return UnpackComparisonOp(depth_test_func);
|
return UnpackComparisonOp(depth_test_func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Maxwell::CullFace CullFace() const noexcept {
|
||||||
|
return UnpackCullFace(cull_face.Value());
|
||||||
|
}
|
||||||
|
|
||||||
|
Maxwell::FrontFace FrontFace() const noexcept {
|
||||||
|
return UnpackFrontFace(front_face.Value());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Maxwell::PrimitiveTopology Topology() const noexcept {
|
||||||
|
return static_cast<Maxwell::PrimitiveTopology>(topology.Value());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ColorBlending {
|
union {
|
||||||
|
u32 raw;
|
||||||
|
BitField<0, 1, u32> no_extended_dynamic_state;
|
||||||
|
BitField<2, 1, u32> primitive_restart_enable;
|
||||||
|
BitField<3, 1, u32> depth_bias_enable;
|
||||||
|
BitField<4, 1, u32> depth_clamp_disabled;
|
||||||
|
BitField<5, 1, u32> ndc_minus_one_to_one;
|
||||||
|
BitField<6, 2, u32> polygon_mode;
|
||||||
|
BitField<8, 5, u32> patch_control_points_minus_one;
|
||||||
|
BitField<13, 2, u32> tessellation_primitive;
|
||||||
|
BitField<15, 2, u32> tessellation_spacing;
|
||||||
|
BitField<17, 1, u32> tessellation_clockwise;
|
||||||
|
BitField<18, 1, u32> logic_op_enable;
|
||||||
|
BitField<19, 4, u32> logic_op;
|
||||||
|
BitField<23, 1, u32> rasterize_enable;
|
||||||
|
};
|
||||||
|
u32 point_size;
|
||||||
|
std::array<u32, Maxwell::NumVertexArrays> binding_divisors;
|
||||||
|
std::array<VertexAttribute, Maxwell::NumVertexAttributes> attributes;
|
||||||
std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
|
std::array<BlendingAttachment, Maxwell::NumRenderTargets> attachments;
|
||||||
|
std::array<u16, Maxwell::NumViewports> viewport_swizzles;
|
||||||
|
DynamicState dynamic_state;
|
||||||
|
|
||||||
void Fill(const Maxwell& regs) noexcept;
|
void Fill(const Maxwell& regs, bool has_extended_dynamic_state);
|
||||||
};
|
|
||||||
|
|
||||||
struct ViewportSwizzles {
|
|
||||||
std::array<u16, Maxwell::NumViewports> swizzles;
|
|
||||||
|
|
||||||
void Fill(const Maxwell& regs) noexcept;
|
|
||||||
};
|
|
||||||
|
|
||||||
VertexInput vertex_input;
|
|
||||||
Rasterizer rasterizer;
|
|
||||||
DepthStencil depth_stencil;
|
|
||||||
ColorBlending color_blending;
|
|
||||||
ViewportSwizzles viewport_swizzles;
|
|
||||||
|
|
||||||
void Fill(const Maxwell& regs);
|
|
||||||
|
|
||||||
std::size_t Hash() const noexcept;
|
std::size_t Hash() const noexcept;
|
||||||
|
|
||||||
|
@ -255,6 +207,11 @@ struct FixedPipelineState {
|
||||||
bool operator!=(const FixedPipelineState& rhs) const noexcept {
|
bool operator!=(const FixedPipelineState& rhs) const noexcept {
|
||||||
return !operator==(rhs);
|
return !operator==(rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t Size() const noexcept {
|
||||||
|
const std::size_t total_size = sizeof *this;
|
||||||
|
return total_size - (no_extended_dynamic_state != 0 ? 0 : sizeof(DynamicState));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
|
static_assert(std::has_unique_object_representations_v<FixedPipelineState>);
|
||||||
static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
|
static_assert(std::is_trivially_copyable_v<FixedPipelineState>);
|
||||||
|
|
|
@ -313,6 +313,16 @@ bool VKDevice::Create() {
|
||||||
LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
|
LOG_INFO(Render_Vulkan, "Device doesn't support custom border colors");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
|
||||||
|
if (ext_extended_dynamic_state) {
|
||||||
|
dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
|
||||||
|
dynamic_state.pNext = nullptr;
|
||||||
|
dynamic_state.extendedDynamicState = VK_TRUE;
|
||||||
|
SetNext(next, dynamic_state);
|
||||||
|
} else {
|
||||||
|
LOG_INFO(Render_Vulkan, "Device doesn't support extended dynamic state");
|
||||||
|
}
|
||||||
|
|
||||||
if (!ext_depth_range_unrestricted) {
|
if (!ext_depth_range_unrestricted) {
|
||||||
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
|
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
|
||||||
}
|
}
|
||||||
|
@ -541,6 +551,7 @@ std::vector<const char*> VKDevice::LoadExtensions() {
|
||||||
bool has_ext_subgroup_size_control{};
|
bool has_ext_subgroup_size_control{};
|
||||||
bool has_ext_transform_feedback{};
|
bool has_ext_transform_feedback{};
|
||||||
bool has_ext_custom_border_color{};
|
bool has_ext_custom_border_color{};
|
||||||
|
bool has_ext_extended_dynamic_state{};
|
||||||
for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) {
|
for (const auto& extension : physical.EnumerateDeviceExtensionProperties()) {
|
||||||
Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
|
Test(extension, nv_viewport_swizzle, VK_NV_VIEWPORT_SWIZZLE_EXTENSION_NAME, true);
|
||||||
Test(extension, khr_uniform_buffer_standard_layout,
|
Test(extension, khr_uniform_buffer_standard_layout,
|
||||||
|
@ -558,6 +569,8 @@ std::vector<const char*> VKDevice::LoadExtensions() {
|
||||||
false);
|
false);
|
||||||
Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME,
|
Test(extension, has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME,
|
||||||
false);
|
false);
|
||||||
|
Test(extension, has_ext_extended_dynamic_state,
|
||||||
|
VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
|
||||||
if (Settings::values.renderer_debug) {
|
if (Settings::values.renderer_debug) {
|
||||||
Test(extension, nv_device_diagnostics_config,
|
Test(extension, nv_device_diagnostics_config,
|
||||||
VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true);
|
VK_NV_DEVICE_DIAGNOSTICS_CONFIG_EXTENSION_NAME, true);
|
||||||
|
@ -643,6 +656,19 @@ std::vector<const char*> VKDevice::LoadExtensions() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (has_ext_extended_dynamic_state) {
|
||||||
|
VkPhysicalDeviceExtendedDynamicStateFeaturesEXT dynamic_state;
|
||||||
|
dynamic_state.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_FEATURES_EXT;
|
||||||
|
dynamic_state.pNext = nullptr;
|
||||||
|
features.pNext = &dynamic_state;
|
||||||
|
physical.GetFeatures2KHR(features);
|
||||||
|
|
||||||
|
if (dynamic_state.extendedDynamicState) {
|
||||||
|
extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
|
||||||
|
ext_extended_dynamic_state = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return extensions;
|
return extensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -182,6 +182,11 @@ public:
|
||||||
return ext_custom_border_color;
|
return ext_custom_border_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if the device supports VK_EXT_extended_dynamic_state.
|
||||||
|
bool IsExtExtendedDynamicStateSupported() const {
|
||||||
|
return ext_extended_dynamic_state;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the vendor name reported from Vulkan.
|
/// Returns the vendor name reported from Vulkan.
|
||||||
std::string_view GetVendorName() const {
|
std::string_view GetVendorName() const {
|
||||||
return vendor_name;
|
return vendor_name;
|
||||||
|
@ -239,6 +244,7 @@ private:
|
||||||
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
|
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
|
||||||
bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
|
bool ext_transform_feedback{}; ///< Support for VK_EXT_transform_feedback.
|
||||||
bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
|
bool ext_custom_border_color{}; ///< Support for VK_EXT_custom_border_color.
|
||||||
|
bool ext_extended_dynamic_state{}; ///< Support for VK_EXT_extended_dynamic_state.
|
||||||
bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
|
bool nv_device_diagnostics_config{}; ///< Support for VK_NV_device_diagnostics_config.
|
||||||
|
|
||||||
// Telemetry parameters
|
// Telemetry parameters
|
||||||
|
|
|
@ -176,20 +176,32 @@ std::vector<vk::ShaderModule> VKGraphicsPipeline::CreateShaderModules(
|
||||||
|
|
||||||
vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
|
vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpass_params,
|
||||||
const SPIRVProgram& program) const {
|
const SPIRVProgram& program) const {
|
||||||
const auto& vi = fixed_state.vertex_input;
|
const auto& state = fixed_state;
|
||||||
const auto& ds = fixed_state.depth_stencil;
|
const auto& viewport_swizzles = state.viewport_swizzles;
|
||||||
const auto& cd = fixed_state.color_blending;
|
|
||||||
const auto& rs = fixed_state.rasterizer;
|
FixedPipelineState::DynamicState dynamic;
|
||||||
const auto& viewport_swizzles = fixed_state.viewport_swizzles.swizzles;
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
|
// Insert dummy values, as long as they are valid they don't matter as extended dynamic
|
||||||
|
// state is ignored
|
||||||
|
dynamic.raw1 = 0;
|
||||||
|
dynamic.raw2 = 0;
|
||||||
|
for (FixedPipelineState::VertexBinding& binding : dynamic.vertex_bindings) {
|
||||||
|
// Enable all vertex bindings
|
||||||
|
binding.raw = 0;
|
||||||
|
binding.enabled.Assign(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dynamic = state.dynamic_state;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<VkVertexInputBindingDescription> vertex_bindings;
|
std::vector<VkVertexInputBindingDescription> vertex_bindings;
|
||||||
std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
|
std::vector<VkVertexInputBindingDivisorDescriptionEXT> vertex_binding_divisors;
|
||||||
for (std::size_t index = 0; index < std::size(vi.bindings); ++index) {
|
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||||
const auto& binding = vi.bindings[index];
|
const auto& binding = dynamic.vertex_bindings[index];
|
||||||
if (!binding.enabled) {
|
if (!binding.enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const bool instanced = vi.binding_divisors[index] != 0;
|
const bool instanced = state.binding_divisors[index] != 0;
|
||||||
const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
|
const auto rate = instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
|
||||||
|
|
||||||
auto& vertex_binding = vertex_bindings.emplace_back();
|
auto& vertex_binding = vertex_bindings.emplace_back();
|
||||||
|
@ -200,14 +212,14 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
|
||||||
if (instanced) {
|
if (instanced) {
|
||||||
auto& binding_divisor = vertex_binding_divisors.emplace_back();
|
auto& binding_divisor = vertex_binding_divisors.emplace_back();
|
||||||
binding_divisor.binding = static_cast<u32>(index);
|
binding_divisor.binding = static_cast<u32>(index);
|
||||||
binding_divisor.divisor = vi.binding_divisors[index];
|
binding_divisor.divisor = state.binding_divisors[index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<VkVertexInputAttributeDescription> vertex_attributes;
|
std::vector<VkVertexInputAttributeDescription> vertex_attributes;
|
||||||
const auto& input_attributes = program[0]->entries.attributes;
|
const auto& input_attributes = program[0]->entries.attributes;
|
||||||
for (std::size_t index = 0; index < std::size(vi.attributes); ++index) {
|
for (std::size_t index = 0; index < state.attributes.size(); ++index) {
|
||||||
const auto& attribute = vi.attributes[index];
|
const auto& attribute = state.attributes[index];
|
||||||
if (!attribute.enabled) {
|
if (!attribute.enabled) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -244,15 +256,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
|
||||||
input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
input_assembly_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||||
input_assembly_ci.pNext = nullptr;
|
input_assembly_ci.pNext = nullptr;
|
||||||
input_assembly_ci.flags = 0;
|
input_assembly_ci.flags = 0;
|
||||||
input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, rs.Topology());
|
input_assembly_ci.topology = MaxwellToVK::PrimitiveTopology(device, dynamic.Topology());
|
||||||
input_assembly_ci.primitiveRestartEnable =
|
input_assembly_ci.primitiveRestartEnable =
|
||||||
rs.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology);
|
state.primitive_restart_enable != 0 && SupportsPrimitiveRestart(input_assembly_ci.topology);
|
||||||
|
|
||||||
VkPipelineTessellationStateCreateInfo tessellation_ci;
|
VkPipelineTessellationStateCreateInfo tessellation_ci;
|
||||||
tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
|
tessellation_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO;
|
||||||
tessellation_ci.pNext = nullptr;
|
tessellation_ci.pNext = nullptr;
|
||||||
tessellation_ci.flags = 0;
|
tessellation_ci.flags = 0;
|
||||||
tessellation_ci.patchControlPoints = rs.patch_control_points_minus_one.Value() + 1;
|
tessellation_ci.patchControlPoints = state.patch_control_points_minus_one.Value() + 1;
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewport_ci;
|
VkPipelineViewportStateCreateInfo viewport_ci;
|
||||||
viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
viewport_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
|
@ -280,13 +292,13 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
|
||||||
rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
rasterization_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
|
||||||
rasterization_ci.pNext = nullptr;
|
rasterization_ci.pNext = nullptr;
|
||||||
rasterization_ci.flags = 0;
|
rasterization_ci.flags = 0;
|
||||||
rasterization_ci.depthClampEnable = rs.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE;
|
rasterization_ci.depthClampEnable = state.depth_clamp_disabled == 0 ? VK_TRUE : VK_FALSE;
|
||||||
rasterization_ci.rasterizerDiscardEnable = rs.rasterize_enable == 0 ? VK_TRUE : VK_FALSE;
|
rasterization_ci.rasterizerDiscardEnable = state.rasterize_enable == 0 ? VK_TRUE : VK_FALSE;
|
||||||
rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL;
|
rasterization_ci.polygonMode = VK_POLYGON_MODE_FILL;
|
||||||
rasterization_ci.cullMode =
|
rasterization_ci.cullMode =
|
||||||
rs.cull_enable ? MaxwellToVK::CullFace(rs.CullFace()) : VK_CULL_MODE_NONE;
|
dynamic.cull_enable ? MaxwellToVK::CullFace(dynamic.CullFace()) : VK_CULL_MODE_NONE;
|
||||||
rasterization_ci.frontFace = MaxwellToVK::FrontFace(rs.FrontFace());
|
rasterization_ci.frontFace = MaxwellToVK::FrontFace(dynamic.FrontFace());
|
||||||
rasterization_ci.depthBiasEnable = rs.depth_bias_enable;
|
rasterization_ci.depthBiasEnable = state.depth_bias_enable;
|
||||||
rasterization_ci.depthBiasConstantFactor = 0.0f;
|
rasterization_ci.depthBiasConstantFactor = 0.0f;
|
||||||
rasterization_ci.depthBiasClamp = 0.0f;
|
rasterization_ci.depthBiasClamp = 0.0f;
|
||||||
rasterization_ci.depthBiasSlopeFactor = 0.0f;
|
rasterization_ci.depthBiasSlopeFactor = 0.0f;
|
||||||
|
@ -307,14 +319,15 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
|
||||||
depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
depth_stencil_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
|
||||||
depth_stencil_ci.pNext = nullptr;
|
depth_stencil_ci.pNext = nullptr;
|
||||||
depth_stencil_ci.flags = 0;
|
depth_stencil_ci.flags = 0;
|
||||||
depth_stencil_ci.depthTestEnable = ds.depth_test_enable;
|
depth_stencil_ci.depthTestEnable = dynamic.depth_test_enable;
|
||||||
depth_stencil_ci.depthWriteEnable = ds.depth_write_enable;
|
depth_stencil_ci.depthWriteEnable = dynamic.depth_write_enable;
|
||||||
depth_stencil_ci.depthCompareOp =
|
depth_stencil_ci.depthCompareOp = dynamic.depth_test_enable
|
||||||
ds.depth_test_enable ? MaxwellToVK::ComparisonOp(ds.DepthTestFunc()) : VK_COMPARE_OP_ALWAYS;
|
? MaxwellToVK::ComparisonOp(dynamic.DepthTestFunc())
|
||||||
depth_stencil_ci.depthBoundsTestEnable = ds.depth_bounds_enable;
|
: VK_COMPARE_OP_ALWAYS;
|
||||||
depth_stencil_ci.stencilTestEnable = ds.stencil_enable;
|
depth_stencil_ci.depthBoundsTestEnable = dynamic.depth_bounds_enable;
|
||||||
depth_stencil_ci.front = GetStencilFaceState(ds.front);
|
depth_stencil_ci.stencilTestEnable = dynamic.stencil_enable;
|
||||||
depth_stencil_ci.back = GetStencilFaceState(ds.back);
|
depth_stencil_ci.front = GetStencilFaceState(dynamic.front);
|
||||||
|
depth_stencil_ci.back = GetStencilFaceState(dynamic.back);
|
||||||
depth_stencil_ci.minDepthBounds = 0.0f;
|
depth_stencil_ci.minDepthBounds = 0.0f;
|
||||||
depth_stencil_ci.maxDepthBounds = 0.0f;
|
depth_stencil_ci.maxDepthBounds = 0.0f;
|
||||||
|
|
||||||
|
@ -324,7 +337,7 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
|
||||||
static constexpr std::array COMPONENT_TABLE = {
|
static constexpr std::array COMPONENT_TABLE = {
|
||||||
VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT,
|
VK_COLOR_COMPONENT_R_BIT, VK_COLOR_COMPONENT_G_BIT, VK_COLOR_COMPONENT_B_BIT,
|
||||||
VK_COLOR_COMPONENT_A_BIT};
|
VK_COLOR_COMPONENT_A_BIT};
|
||||||
const auto& blend = cd.attachments[index];
|
const auto& blend = state.attachments[index];
|
||||||
|
|
||||||
VkColorComponentFlags color_components = 0;
|
VkColorComponentFlags color_components = 0;
|
||||||
for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) {
|
for (std::size_t i = 0; i < COMPONENT_TABLE.size(); ++i) {
|
||||||
|
@ -354,11 +367,27 @@ vk::Pipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& renderpa
|
||||||
color_blend_ci.pAttachments = cb_attachments.data();
|
color_blend_ci.pAttachments = cb_attachments.data();
|
||||||
std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants));
|
std::memset(color_blend_ci.blendConstants, 0, sizeof(color_blend_ci.blendConstants));
|
||||||
|
|
||||||
static constexpr std::array dynamic_states = {
|
std::vector dynamic_states = {
|
||||||
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
|
VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR,
|
||||||
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
VK_DYNAMIC_STATE_DEPTH_BIAS, VK_DYNAMIC_STATE_BLEND_CONSTANTS,
|
||||||
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
VK_DYNAMIC_STATE_DEPTH_BOUNDS, VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK,
|
||||||
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE};
|
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, VK_DYNAMIC_STATE_STENCIL_REFERENCE,
|
||||||
|
};
|
||||||
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
|
static constexpr std::array extended = {
|
||||||
|
VK_DYNAMIC_STATE_CULL_MODE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_FRONT_FACE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY_EXT,
|
||||||
|
VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP_EXT,
|
||||||
|
VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE_EXT,
|
||||||
|
VK_DYNAMIC_STATE_STENCIL_OP_EXT,
|
||||||
|
};
|
||||||
|
dynamic_states.insert(dynamic_states.end(), extended.begin(), extended.end());
|
||||||
|
}
|
||||||
|
|
||||||
VkPipelineDynamicStateCreateInfo dynamic_state_ci;
|
VkPipelineDynamicStateCreateInfo dynamic_state_ci;
|
||||||
dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
dynamic_state_ci.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||||
|
|
|
@ -116,12 +116,12 @@ u32 FillDescriptorLayout(const ShaderEntries& entries,
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
std::size_t GraphicsPipelineCacheKey::Hash() const noexcept {
|
std::size_t GraphicsPipelineCacheKey::Hash() const noexcept {
|
||||||
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), sizeof *this);
|
const u64 hash = Common::CityHash64(reinterpret_cast<const char*>(this), Size());
|
||||||
return static_cast<std::size_t>(hash);
|
return static_cast<std::size_t>(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
|
bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) const noexcept {
|
||||||
return std::memcmp(&rhs, this, sizeof *this) == 0;
|
return std::memcmp(&rhs, this, Size()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t ComputePipelineCacheKey::Hash() const noexcept {
|
std::size_t ComputePipelineCacheKey::Hash() const noexcept {
|
||||||
|
@ -312,18 +312,19 @@ VKPipelineCache::DecompileShaders(const GraphicsPipelineCacheKey& key) {
|
||||||
const auto& gpu = system.GPU().Maxwell3D();
|
const auto& gpu = system.GPU().Maxwell3D();
|
||||||
|
|
||||||
Specialization specialization;
|
Specialization specialization;
|
||||||
if (fixed_state.rasterizer.Topology() == Maxwell::PrimitiveTopology::Points) {
|
if (fixed_state.dynamic_state.Topology() == Maxwell::PrimitiveTopology::Points ||
|
||||||
|
device.IsExtExtendedDynamicStateSupported()) {
|
||||||
float point_size;
|
float point_size;
|
||||||
std::memcpy(&point_size, &fixed_state.rasterizer.point_size, sizeof(float));
|
std::memcpy(&point_size, &fixed_state.point_size, sizeof(float));
|
||||||
specialization.point_size = point_size;
|
specialization.point_size = point_size;
|
||||||
ASSERT(point_size != 0.0f);
|
ASSERT(point_size != 0.0f);
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
|
for (std::size_t i = 0; i < Maxwell::NumVertexAttributes; ++i) {
|
||||||
const auto& attribute = fixed_state.vertex_input.attributes[i];
|
const auto& attribute = fixed_state.attributes[i];
|
||||||
specialization.enabled_attributes[i] = attribute.enabled.Value() != 0;
|
specialization.enabled_attributes[i] = attribute.enabled.Value() != 0;
|
||||||
specialization.attribute_types[i] = attribute.Type();
|
specialization.attribute_types[i] = attribute.Type();
|
||||||
}
|
}
|
||||||
specialization.ndc_minus_one_to_one = fixed_state.rasterizer.ndc_minus_one_to_one;
|
specialization.ndc_minus_one_to_one = fixed_state.ndc_minus_one_to_one;
|
||||||
|
|
||||||
SPIRVProgram program;
|
SPIRVProgram program;
|
||||||
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
||||||
|
|
|
@ -44,10 +44,10 @@ class VKUpdateDescriptorQueue;
|
||||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||||
|
|
||||||
struct GraphicsPipelineCacheKey {
|
struct GraphicsPipelineCacheKey {
|
||||||
FixedPipelineState fixed_state;
|
|
||||||
RenderPassParams renderpass_params;
|
RenderPassParams renderpass_params;
|
||||||
|
u32 padding;
|
||||||
std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
|
std::array<GPUVAddr, Maxwell::MaxShaderProgram> shaders;
|
||||||
u64 padding; // This is necessary for unique object representations
|
FixedPipelineState fixed_state;
|
||||||
|
|
||||||
std::size_t Hash() const noexcept;
|
std::size_t Hash() const noexcept;
|
||||||
|
|
||||||
|
@ -56,6 +56,10 @@ struct GraphicsPipelineCacheKey {
|
||||||
bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
|
bool operator!=(const GraphicsPipelineCacheKey& rhs) const noexcept {
|
||||||
return !operator==(rhs);
|
return !operator==(rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::size_t Size() const noexcept {
|
||||||
|
return sizeof(renderpass_params) + sizeof(padding) + sizeof(shaders) + fixed_state.Size();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
|
static_assert(std::has_unique_object_representations_v<GraphicsPipelineCacheKey>);
|
||||||
static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
|
static_assert(std::is_trivially_copyable_v<GraphicsPipelineCacheKey>);
|
||||||
|
|
|
@ -186,13 +186,22 @@ bool HasToPreserveDepthContents(bool is_clear, const Maxwell& regs) {
|
||||||
scissor.max_y < regs.zeta_height;
|
scissor.max_y < regs.zeta_height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <std::size_t N>
|
||||||
|
std::array<VkDeviceSize, N> ExpandStrides(const std::array<u16, N>& strides) {
|
||||||
|
std::array<VkDeviceSize, N> expanded;
|
||||||
|
std::copy(strides.begin(), strides.end(), expanded.begin());
|
||||||
|
return expanded;
|
||||||
|
}
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
class BufferBindings final {
|
class BufferBindings final {
|
||||||
public:
|
public:
|
||||||
void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset) {
|
void AddVertexBinding(VkBuffer buffer, VkDeviceSize offset, VkDeviceSize size, u32 stride) {
|
||||||
vertex.buffers[vertex.num_buffers] = buffer;
|
vertex.buffers[vertex.num_buffers] = buffer;
|
||||||
vertex.offsets[vertex.num_buffers] = offset;
|
vertex.offsets[vertex.num_buffers] = offset;
|
||||||
|
vertex.sizes[vertex.num_buffers] = size;
|
||||||
|
vertex.strides[vertex.num_buffers] = static_cast<u16>(stride);
|
||||||
++vertex.num_buffers;
|
++vertex.num_buffers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,76 +211,76 @@ public:
|
||||||
index.type = type;
|
index.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bind(VKScheduler& scheduler) const {
|
void Bind(const VKDevice& device, VKScheduler& scheduler) const {
|
||||||
// Use this large switch case to avoid dispatching more memory in the record lambda than
|
// Use this large switch case to avoid dispatching more memory in the record lambda than
|
||||||
// what we need. It looks horrible, but it's the best we can do on standard C++.
|
// what we need. It looks horrible, but it's the best we can do on standard C++.
|
||||||
switch (vertex.num_buffers) {
|
switch (vertex.num_buffers) {
|
||||||
case 0:
|
case 0:
|
||||||
return BindStatic<0>(scheduler);
|
return BindStatic<0>(device, scheduler);
|
||||||
case 1:
|
case 1:
|
||||||
return BindStatic<1>(scheduler);
|
return BindStatic<1>(device, scheduler);
|
||||||
case 2:
|
case 2:
|
||||||
return BindStatic<2>(scheduler);
|
return BindStatic<2>(device, scheduler);
|
||||||
case 3:
|
case 3:
|
||||||
return BindStatic<3>(scheduler);
|
return BindStatic<3>(device, scheduler);
|
||||||
case 4:
|
case 4:
|
||||||
return BindStatic<4>(scheduler);
|
return BindStatic<4>(device, scheduler);
|
||||||
case 5:
|
case 5:
|
||||||
return BindStatic<5>(scheduler);
|
return BindStatic<5>(device, scheduler);
|
||||||
case 6:
|
case 6:
|
||||||
return BindStatic<6>(scheduler);
|
return BindStatic<6>(device, scheduler);
|
||||||
case 7:
|
case 7:
|
||||||
return BindStatic<7>(scheduler);
|
return BindStatic<7>(device, scheduler);
|
||||||
case 8:
|
case 8:
|
||||||
return BindStatic<8>(scheduler);
|
return BindStatic<8>(device, scheduler);
|
||||||
case 9:
|
case 9:
|
||||||
return BindStatic<9>(scheduler);
|
return BindStatic<9>(device, scheduler);
|
||||||
case 10:
|
case 10:
|
||||||
return BindStatic<10>(scheduler);
|
return BindStatic<10>(device, scheduler);
|
||||||
case 11:
|
case 11:
|
||||||
return BindStatic<11>(scheduler);
|
return BindStatic<11>(device, scheduler);
|
||||||
case 12:
|
case 12:
|
||||||
return BindStatic<12>(scheduler);
|
return BindStatic<12>(device, scheduler);
|
||||||
case 13:
|
case 13:
|
||||||
return BindStatic<13>(scheduler);
|
return BindStatic<13>(device, scheduler);
|
||||||
case 14:
|
case 14:
|
||||||
return BindStatic<14>(scheduler);
|
return BindStatic<14>(device, scheduler);
|
||||||
case 15:
|
case 15:
|
||||||
return BindStatic<15>(scheduler);
|
return BindStatic<15>(device, scheduler);
|
||||||
case 16:
|
case 16:
|
||||||
return BindStatic<16>(scheduler);
|
return BindStatic<16>(device, scheduler);
|
||||||
case 17:
|
case 17:
|
||||||
return BindStatic<17>(scheduler);
|
return BindStatic<17>(device, scheduler);
|
||||||
case 18:
|
case 18:
|
||||||
return BindStatic<18>(scheduler);
|
return BindStatic<18>(device, scheduler);
|
||||||
case 19:
|
case 19:
|
||||||
return BindStatic<19>(scheduler);
|
return BindStatic<19>(device, scheduler);
|
||||||
case 20:
|
case 20:
|
||||||
return BindStatic<20>(scheduler);
|
return BindStatic<20>(device, scheduler);
|
||||||
case 21:
|
case 21:
|
||||||
return BindStatic<21>(scheduler);
|
return BindStatic<21>(device, scheduler);
|
||||||
case 22:
|
case 22:
|
||||||
return BindStatic<22>(scheduler);
|
return BindStatic<22>(device, scheduler);
|
||||||
case 23:
|
case 23:
|
||||||
return BindStatic<23>(scheduler);
|
return BindStatic<23>(device, scheduler);
|
||||||
case 24:
|
case 24:
|
||||||
return BindStatic<24>(scheduler);
|
return BindStatic<24>(device, scheduler);
|
||||||
case 25:
|
case 25:
|
||||||
return BindStatic<25>(scheduler);
|
return BindStatic<25>(device, scheduler);
|
||||||
case 26:
|
case 26:
|
||||||
return BindStatic<26>(scheduler);
|
return BindStatic<26>(device, scheduler);
|
||||||
case 27:
|
case 27:
|
||||||
return BindStatic<27>(scheduler);
|
return BindStatic<27>(device, scheduler);
|
||||||
case 28:
|
case 28:
|
||||||
return BindStatic<28>(scheduler);
|
return BindStatic<28>(device, scheduler);
|
||||||
case 29:
|
case 29:
|
||||||
return BindStatic<29>(scheduler);
|
return BindStatic<29>(device, scheduler);
|
||||||
case 30:
|
case 30:
|
||||||
return BindStatic<30>(scheduler);
|
return BindStatic<30>(device, scheduler);
|
||||||
case 31:
|
case 31:
|
||||||
return BindStatic<31>(scheduler);
|
return BindStatic<31>(device, scheduler);
|
||||||
case 32:
|
case 32:
|
||||||
return BindStatic<32>(scheduler);
|
return BindStatic<32>(device, scheduler);
|
||||||
}
|
}
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
@ -282,6 +291,8 @@ private:
|
||||||
std::size_t num_buffers = 0;
|
std::size_t num_buffers = 0;
|
||||||
std::array<VkBuffer, Maxwell::NumVertexArrays> buffers;
|
std::array<VkBuffer, Maxwell::NumVertexArrays> buffers;
|
||||||
std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets;
|
std::array<VkDeviceSize, Maxwell::NumVertexArrays> offsets;
|
||||||
|
std::array<VkDeviceSize, Maxwell::NumVertexArrays> sizes;
|
||||||
|
std::array<u16, Maxwell::NumVertexArrays> strides;
|
||||||
} vertex;
|
} vertex;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
@ -291,15 +302,23 @@ private:
|
||||||
} index;
|
} index;
|
||||||
|
|
||||||
template <std::size_t N>
|
template <std::size_t N>
|
||||||
void BindStatic(VKScheduler& scheduler) const {
|
void BindStatic(const VKDevice& device, VKScheduler& scheduler) const {
|
||||||
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
if (index.buffer) {
|
if (index.buffer) {
|
||||||
BindStatic<N, true>(scheduler);
|
BindStatic<N, true, true>(scheduler);
|
||||||
} else {
|
} else {
|
||||||
BindStatic<N, false>(scheduler);
|
BindStatic<N, false, true>(scheduler);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (index.buffer) {
|
||||||
|
BindStatic<N, true, false>(scheduler);
|
||||||
|
} else {
|
||||||
|
BindStatic<N, false, false>(scheduler);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <std::size_t N, bool is_indexed>
|
template <std::size_t N, bool is_indexed, bool has_extended_dynamic_state>
|
||||||
void BindStatic(VKScheduler& scheduler) const {
|
void BindStatic(VKScheduler& scheduler) const {
|
||||||
static_assert(N <= Maxwell::NumVertexArrays);
|
static_assert(N <= Maxwell::NumVertexArrays);
|
||||||
if constexpr (N == 0) {
|
if constexpr (N == 0) {
|
||||||
|
@ -311,6 +330,31 @@ private:
|
||||||
std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin());
|
std::copy(vertex.buffers.begin(), vertex.buffers.begin() + N, buffers.begin());
|
||||||
std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
|
std::copy(vertex.offsets.begin(), vertex.offsets.begin() + N, offsets.begin());
|
||||||
|
|
||||||
|
if constexpr (has_extended_dynamic_state) {
|
||||||
|
// With extended dynamic states we can specify the length and stride of a vertex buffer
|
||||||
|
// std::array<VkDeviceSize, N> sizes;
|
||||||
|
std::array<u16, N> strides;
|
||||||
|
// std::copy(vertex.sizes.begin(), vertex.sizes.begin() + N, sizes.begin());
|
||||||
|
std::copy(vertex.strides.begin(), vertex.strides.begin() + N, strides.begin());
|
||||||
|
|
||||||
|
if constexpr (is_indexed) {
|
||||||
|
scheduler.Record(
|
||||||
|
[buffers, offsets, strides, index = index](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.BindIndexBuffer(index.buffer, index.offset, index.type);
|
||||||
|
cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
|
||||||
|
offsets.data(), nullptr,
|
||||||
|
ExpandStrides(strides).data());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
scheduler.Record([buffers, offsets, strides](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.BindVertexBuffers2EXT(0, static_cast<u32>(N), buffers.data(),
|
||||||
|
offsets.data(), nullptr,
|
||||||
|
ExpandStrides(strides).data());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if constexpr (is_indexed) {
|
if constexpr (is_indexed) {
|
||||||
// Indexed draw
|
// Indexed draw
|
||||||
scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([buffers, offsets, index = index](vk::CommandBuffer cmdbuf) {
|
||||||
|
@ -369,7 +413,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
|
||||||
|
|
||||||
const auto& gpu = system.GPU().Maxwell3D();
|
const auto& gpu = system.GPU().Maxwell3D();
|
||||||
GraphicsPipelineCacheKey key;
|
GraphicsPipelineCacheKey key;
|
||||||
key.fixed_state.Fill(gpu.regs);
|
key.fixed_state.Fill(gpu.regs, device.IsExtExtendedDynamicStateSupported());
|
||||||
|
|
||||||
buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
|
buffer_cache.Map(CalculateGraphicsStreamBufferSize(is_indexed));
|
||||||
|
|
||||||
|
@ -402,7 +446,7 @@ void RasterizerVulkan::Draw(bool is_indexed, bool is_instanced) {
|
||||||
|
|
||||||
UpdateDynamicStates();
|
UpdateDynamicStates();
|
||||||
|
|
||||||
buffer_bindings.Bind(scheduler);
|
buffer_bindings.Bind(device, scheduler);
|
||||||
|
|
||||||
BeginTransformFeedback();
|
BeginTransformFeedback();
|
||||||
|
|
||||||
|
@ -822,7 +866,7 @@ RasterizerVulkan::DrawParameters RasterizerVulkan::SetupGeometry(FixedPipelineSt
|
||||||
const auto& gpu = system.GPU().Maxwell3D();
|
const auto& gpu = system.GPU().Maxwell3D();
|
||||||
const auto& regs = gpu.regs;
|
const auto& regs = gpu.regs;
|
||||||
|
|
||||||
SetupVertexArrays(fixed_state.vertex_input, buffer_bindings);
|
SetupVertexArrays(buffer_bindings);
|
||||||
|
|
||||||
const u32 base_instance = regs.vb_base_instance;
|
const u32 base_instance = regs.vb_base_instance;
|
||||||
const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
|
const u32 num_instances = is_instanced ? gpu.mme_draw.instance_count : 1;
|
||||||
|
@ -893,6 +937,17 @@ void RasterizerVulkan::UpdateDynamicStates() {
|
||||||
UpdateBlendConstants(regs);
|
UpdateBlendConstants(regs);
|
||||||
UpdateDepthBounds(regs);
|
UpdateDepthBounds(regs);
|
||||||
UpdateStencilFaces(regs);
|
UpdateStencilFaces(regs);
|
||||||
|
if (device.IsExtExtendedDynamicStateSupported()) {
|
||||||
|
UpdateCullMode(regs);
|
||||||
|
UpdateDepthBoundsTestEnable(regs);
|
||||||
|
UpdateDepthTestEnable(regs);
|
||||||
|
UpdateDepthWriteEnable(regs);
|
||||||
|
UpdateDepthCompareOp(regs);
|
||||||
|
UpdateFrontFace(regs);
|
||||||
|
UpdatePrimitiveTopology(regs);
|
||||||
|
UpdateStencilOp(regs);
|
||||||
|
UpdateStencilTestEnable(regs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::BeginTransformFeedback() {
|
void RasterizerVulkan::BeginTransformFeedback() {
|
||||||
|
@ -940,41 +995,25 @@ void RasterizerVulkan::EndTransformFeedback() {
|
||||||
[](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
|
[](vk::CommandBuffer cmdbuf) { cmdbuf.EndTransformFeedbackEXT(0, 0, nullptr, nullptr); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void RasterizerVulkan::SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
|
void RasterizerVulkan::SetupVertexArrays(BufferBindings& buffer_bindings) {
|
||||||
BufferBindings& buffer_bindings) {
|
|
||||||
const auto& regs = system.GPU().Maxwell3D().regs;
|
const auto& regs = system.GPU().Maxwell3D().regs;
|
||||||
|
|
||||||
for (std::size_t index = 0; index < Maxwell::NumVertexAttributes; ++index) {
|
|
||||||
const auto& attrib = regs.vertex_attrib_format[index];
|
|
||||||
if (attrib.IsConstant()) {
|
|
||||||
vertex_input.SetAttribute(index, false, 0, 0, {}, {});
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
vertex_input.SetAttribute(index, true, attrib.buffer, attrib.offset, attrib.type.Value(),
|
|
||||||
attrib.size.Value());
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
for (std::size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
|
||||||
const auto& vertex_array = regs.vertex_array[index];
|
const auto& vertex_array = regs.vertex_array[index];
|
||||||
if (!vertex_array.IsEnabled()) {
|
if (!vertex_array.IsEnabled()) {
|
||||||
vertex_input.SetBinding(index, false, 0, 0);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
vertex_input.SetBinding(
|
|
||||||
index, true, vertex_array.stride,
|
|
||||||
regs.instanced_arrays.IsInstancingEnabled(index) ? vertex_array.divisor : 0);
|
|
||||||
|
|
||||||
const GPUVAddr start{vertex_array.StartAddress()};
|
const GPUVAddr start{vertex_array.StartAddress()};
|
||||||
const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
|
const GPUVAddr end{regs.vertex_array_limit[index].LimitAddress()};
|
||||||
|
|
||||||
ASSERT(end >= start);
|
ASSERT(end >= start);
|
||||||
const std::size_t size{end - start};
|
const std::size_t size = end - start;
|
||||||
if (size == 0) {
|
if (size == 0) {
|
||||||
buffer_bindings.AddVertexBinding(DefaultBuffer(), 0);
|
buffer_bindings.AddVertexBinding(DefaultBuffer(), 0, DEFAULT_BUFFER_SIZE, 0);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const auto info = buffer_cache.UploadMemory(start, size);
|
const auto info = buffer_cache.UploadMemory(start, size);
|
||||||
buffer_bindings.AddVertexBinding(info.handle, info.offset);
|
buffer_bindings.AddVertexBinding(info.handle, info.offset, size, vertex_array.stride);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1326,6 +1365,117 @@ void RasterizerVulkan::UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchCullMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduler.Record(
|
||||||
|
[enabled = regs.cull_test_enabled, cull_face = regs.cull_face](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetCullModeEXT(enabled ? MaxwellToVK::CullFace(cull_face) : VK_CULL_MODE_NONE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchDepthBoundsTestEnable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduler.Record([enable = regs.depth_bounds_enable](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetDepthBoundsTestEnableEXT(enable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchDepthTestEnable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduler.Record([enable = regs.depth_test_enable](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetDepthTestEnableEXT(enable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchDepthWriteEnable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduler.Record([enable = regs.depth_write_enabled](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetDepthWriteEnableEXT(enable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchDepthCompareOp()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduler.Record([func = regs.depth_test_func](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetDepthCompareOpEXT(MaxwellToVK::ComparisonOp(func));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchFrontFace()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFrontFace front_face = MaxwellToVK::FrontFace(regs.front_face);
|
||||||
|
if (regs.screen_y_control.triangle_rast_flip != 0) {
|
||||||
|
front_face = front_face == VK_FRONT_FACE_CLOCKWISE ? VK_FRONT_FACE_COUNTER_CLOCKWISE
|
||||||
|
: VK_FRONT_FACE_CLOCKWISE;
|
||||||
|
}
|
||||||
|
scheduler.Record(
|
||||||
|
[front_face](vk::CommandBuffer cmdbuf) { cmdbuf.SetFrontFaceEXT(front_face); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchPrimitiveTopology()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Maxwell::PrimitiveTopology primitive_topology = regs.draw.topology.Value();
|
||||||
|
scheduler.Record([this, primitive_topology](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetPrimitiveTopologyEXT(MaxwellToVK::PrimitiveTopology(device, primitive_topology));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchStencilOp()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const Maxwell::StencilOp fail = regs.stencil_front_op_fail;
|
||||||
|
const Maxwell::StencilOp zfail = regs.stencil_front_op_zfail;
|
||||||
|
const Maxwell::StencilOp zpass = regs.stencil_front_op_zpass;
|
||||||
|
const Maxwell::ComparisonOp compare = regs.stencil_front_func_func;
|
||||||
|
if (regs.stencil_two_side_enable) {
|
||||||
|
scheduler.Record([fail, zfail, zpass, compare](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_AND_BACK, MaxwellToVK::StencilOp(fail),
|
||||||
|
MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
|
||||||
|
MaxwellToVK::ComparisonOp(compare));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const Maxwell::StencilOp back_fail = regs.stencil_back_op_fail;
|
||||||
|
const Maxwell::StencilOp back_zfail = regs.stencil_back_op_zfail;
|
||||||
|
const Maxwell::StencilOp back_zpass = regs.stencil_back_op_zpass;
|
||||||
|
const Maxwell::ComparisonOp back_compare = regs.stencil_back_func_func;
|
||||||
|
scheduler.Record([fail, zfail, zpass, compare, back_fail, back_zfail, back_zpass,
|
||||||
|
back_compare](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_FRONT_BIT, MaxwellToVK::StencilOp(fail),
|
||||||
|
MaxwellToVK::StencilOp(zpass), MaxwellToVK::StencilOp(zfail),
|
||||||
|
MaxwellToVK::ComparisonOp(compare));
|
||||||
|
cmdbuf.SetStencilOpEXT(VK_STENCIL_FACE_BACK_BIT, MaxwellToVK::StencilOp(back_fail),
|
||||||
|
MaxwellToVK::StencilOp(back_zpass),
|
||||||
|
MaxwellToVK::StencilOp(back_zfail),
|
||||||
|
MaxwellToVK::ComparisonOp(back_compare));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RasterizerVulkan::UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs) {
|
||||||
|
if (!state_tracker.TouchStencilTestEnable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
scheduler.Record([enable = regs.stencil_enable](vk::CommandBuffer cmdbuf) {
|
||||||
|
cmdbuf.SetStencilTestEnableEXT(enable);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
|
std::size_t RasterizerVulkan::CalculateGraphicsStreamBufferSize(bool is_indexed) const {
|
||||||
std::size_t size = CalculateVertexArraysSize();
|
std::size_t size = CalculateVertexArraysSize();
|
||||||
if (is_indexed) {
|
if (is_indexed) {
|
||||||
|
|
|
@ -185,8 +185,7 @@ private:
|
||||||
|
|
||||||
bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
|
bool WalkAttachmentOverlaps(const CachedSurfaceView& attachment);
|
||||||
|
|
||||||
void SetupVertexArrays(FixedPipelineState::VertexInput& vertex_input,
|
void SetupVertexArrays(BufferBindings& buffer_bindings);
|
||||||
BufferBindings& buffer_bindings);
|
|
||||||
|
|
||||||
void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
|
void SetupIndexBuffer(BufferBindings& buffer_bindings, DrawParameters& params, bool is_indexed);
|
||||||
|
|
||||||
|
@ -246,6 +245,16 @@ private:
|
||||||
void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
|
void UpdateDepthBounds(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
|
void UpdateStencilFaces(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
|
||||||
|
void UpdateCullMode(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateDepthBoundsTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateDepthTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateDepthWriteEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateDepthCompareOp(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateFrontFace(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdatePrimitiveTopology(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateStencilOp(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
void UpdateStencilTestEnable(Tegra::Engines::Maxwell3D::Regs& regs);
|
||||||
|
|
||||||
std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
|
std::size_t CalculateGraphicsStreamBufferSize(bool is_indexed) const;
|
||||||
|
|
||||||
std::size_t CalculateComputeStreamBufferSize() const;
|
std::size_t CalculateComputeStreamBufferSize() const;
|
||||||
|
|
|
@ -36,6 +36,15 @@ Flags MakeInvalidationFlags() {
|
||||||
flags[BlendConstants] = true;
|
flags[BlendConstants] = true;
|
||||||
flags[DepthBounds] = true;
|
flags[DepthBounds] = true;
|
||||||
flags[StencilProperties] = true;
|
flags[StencilProperties] = true;
|
||||||
|
flags[CullMode] = true;
|
||||||
|
flags[DepthBoundsEnable] = true;
|
||||||
|
flags[DepthTestEnable] = true;
|
||||||
|
flags[DepthWriteEnable] = true;
|
||||||
|
flags[DepthCompareOp] = true;
|
||||||
|
flags[FrontFace] = true;
|
||||||
|
flags[PrimitiveTopology] = true;
|
||||||
|
flags[StencilOp] = true;
|
||||||
|
flags[StencilTestEnable] = true;
|
||||||
return flags;
|
return flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +84,57 @@ void SetupDirtyStencilProperties(Tables& tables) {
|
||||||
table[OFF(stencil_back_func_mask)] = StencilProperties;
|
table[OFF(stencil_back_func_mask)] = StencilProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetupDirtyCullMode(Tables& tables) {
|
||||||
|
auto& table = tables[0];
|
||||||
|
table[OFF(cull_face)] = CullMode;
|
||||||
|
table[OFF(cull_test_enabled)] = CullMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyDepthBoundsEnable(Tables& tables) {
|
||||||
|
tables[0][OFF(depth_bounds_enable)] = DepthBoundsEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyDepthTestEnable(Tables& tables) {
|
||||||
|
tables[0][OFF(depth_test_enable)] = DepthTestEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyDepthWriteEnable(Tables& tables) {
|
||||||
|
tables[0][OFF(depth_write_enabled)] = DepthWriteEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyDepthCompareOp(Tables& tables) {
|
||||||
|
tables[0][OFF(depth_test_func)] = DepthCompareOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyFrontFace(Tables& tables) {
|
||||||
|
auto& table = tables[0];
|
||||||
|
table[OFF(front_face)] = FrontFace;
|
||||||
|
table[OFF(screen_y_control)] = FrontFace;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyPrimitiveTopology(Tables& tables) {
|
||||||
|
tables[0][OFF(draw.topology)] = PrimitiveTopology;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyStencilOp(Tables& tables) {
|
||||||
|
auto& table = tables[0];
|
||||||
|
table[OFF(stencil_front_op_fail)] = StencilOp;
|
||||||
|
table[OFF(stencil_front_op_zfail)] = StencilOp;
|
||||||
|
table[OFF(stencil_front_op_zpass)] = StencilOp;
|
||||||
|
table[OFF(stencil_front_func_func)] = StencilOp;
|
||||||
|
table[OFF(stencil_back_op_fail)] = StencilOp;
|
||||||
|
table[OFF(stencil_back_op_zfail)] = StencilOp;
|
||||||
|
table[OFF(stencil_back_op_zpass)] = StencilOp;
|
||||||
|
table[OFF(stencil_back_func_func)] = StencilOp;
|
||||||
|
|
||||||
|
// Table 0 is used by StencilProperties
|
||||||
|
tables[1][OFF(stencil_two_side_enable)] = StencilOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupDirtyStencilTestEnable(Tables& tables) {
|
||||||
|
tables[0][OFF(stencil_enable)] = StencilTestEnable;
|
||||||
|
}
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
StateTracker::StateTracker(Core::System& system)
|
StateTracker::StateTracker(Core::System& system)
|
||||||
|
@ -90,6 +150,14 @@ void StateTracker::Initialize() {
|
||||||
SetupDirtyBlendConstants(tables);
|
SetupDirtyBlendConstants(tables);
|
||||||
SetupDirtyDepthBounds(tables);
|
SetupDirtyDepthBounds(tables);
|
||||||
SetupDirtyStencilProperties(tables);
|
SetupDirtyStencilProperties(tables);
|
||||||
|
SetupDirtyCullMode(tables);
|
||||||
|
SetupDirtyDepthBoundsEnable(tables);
|
||||||
|
SetupDirtyDepthTestEnable(tables);
|
||||||
|
SetupDirtyDepthWriteEnable(tables);
|
||||||
|
SetupDirtyDepthCompareOp(tables);
|
||||||
|
SetupDirtyFrontFace(tables);
|
||||||
|
SetupDirtyPrimitiveTopology(tables);
|
||||||
|
SetupDirtyStencilOp(tables);
|
||||||
}
|
}
|
||||||
|
|
||||||
void StateTracker::InvalidateCommandBufferState() {
|
void StateTracker::InvalidateCommandBufferState() {
|
||||||
|
|
|
@ -26,6 +26,16 @@ enum : u8 {
|
||||||
DepthBounds,
|
DepthBounds,
|
||||||
StencilProperties,
|
StencilProperties,
|
||||||
|
|
||||||
|
CullMode,
|
||||||
|
DepthBoundsEnable,
|
||||||
|
DepthTestEnable,
|
||||||
|
DepthWriteEnable,
|
||||||
|
DepthCompareOp,
|
||||||
|
FrontFace,
|
||||||
|
PrimitiveTopology,
|
||||||
|
StencilOp,
|
||||||
|
StencilTestEnable,
|
||||||
|
|
||||||
Last
|
Last
|
||||||
};
|
};
|
||||||
static_assert(Last <= std::numeric_limits<u8>::max());
|
static_assert(Last <= std::numeric_limits<u8>::max());
|
||||||
|
@ -64,6 +74,46 @@ public:
|
||||||
return Exchange(Dirty::StencilProperties, false);
|
return Exchange(Dirty::StencilProperties, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TouchCullMode() {
|
||||||
|
return Exchange(Dirty::CullMode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchDepthBoundsTestEnable() {
|
||||||
|
return Exchange(Dirty::DepthBoundsEnable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchDepthTestEnable() {
|
||||||
|
return Exchange(Dirty::DepthTestEnable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchDepthBoundsEnable() {
|
||||||
|
return Exchange(Dirty::DepthBoundsEnable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchDepthWriteEnable() {
|
||||||
|
return Exchange(Dirty::DepthWriteEnable, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchDepthCompareOp() {
|
||||||
|
return Exchange(Dirty::DepthCompareOp, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchFrontFace() {
|
||||||
|
return Exchange(Dirty::FrontFace, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchPrimitiveTopology() {
|
||||||
|
return Exchange(Dirty::PrimitiveTopology, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchStencilOp() {
|
||||||
|
return Exchange(Dirty::StencilOp, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TouchStencilTestEnable() {
|
||||||
|
return Exchange(Dirty::StencilTestEnable, false);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool Exchange(std::size_t id, bool new_value) const noexcept {
|
bool Exchange(std::size_t id, bool new_value) const noexcept {
|
||||||
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
auto& flags = system.GPU().Maxwell3D().dirty.flags;
|
||||||
|
|
|
@ -88,6 +88,16 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
|
||||||
X(vkCmdSetStencilWriteMask);
|
X(vkCmdSetStencilWriteMask);
|
||||||
X(vkCmdSetViewport);
|
X(vkCmdSetViewport);
|
||||||
X(vkCmdWaitEvents);
|
X(vkCmdWaitEvents);
|
||||||
|
X(vkCmdBindVertexBuffers2EXT);
|
||||||
|
X(vkCmdSetCullModeEXT);
|
||||||
|
X(vkCmdSetDepthBoundsTestEnableEXT);
|
||||||
|
X(vkCmdSetDepthCompareOpEXT);
|
||||||
|
X(vkCmdSetDepthTestEnableEXT);
|
||||||
|
X(vkCmdSetDepthWriteEnableEXT);
|
||||||
|
X(vkCmdSetFrontFaceEXT);
|
||||||
|
X(vkCmdSetPrimitiveTopologyEXT);
|
||||||
|
X(vkCmdSetStencilOpEXT);
|
||||||
|
X(vkCmdSetStencilTestEnableEXT);
|
||||||
X(vkCreateBuffer);
|
X(vkCreateBuffer);
|
||||||
X(vkCreateBufferView);
|
X(vkCreateBufferView);
|
||||||
X(vkCreateCommandPool);
|
X(vkCreateCommandPool);
|
||||||
|
|
|
@ -207,6 +207,16 @@ struct DeviceDispatch : public InstanceDispatch {
|
||||||
PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
|
PFN_vkCmdSetStencilWriteMask vkCmdSetStencilWriteMask;
|
||||||
PFN_vkCmdSetViewport vkCmdSetViewport;
|
PFN_vkCmdSetViewport vkCmdSetViewport;
|
||||||
PFN_vkCmdWaitEvents vkCmdWaitEvents;
|
PFN_vkCmdWaitEvents vkCmdWaitEvents;
|
||||||
|
PFN_vkCmdBindVertexBuffers2EXT vkCmdBindVertexBuffers2EXT;
|
||||||
|
PFN_vkCmdSetCullModeEXT vkCmdSetCullModeEXT;
|
||||||
|
PFN_vkCmdSetDepthBoundsTestEnableEXT vkCmdSetDepthBoundsTestEnableEXT;
|
||||||
|
PFN_vkCmdSetDepthCompareOpEXT vkCmdSetDepthCompareOpEXT;
|
||||||
|
PFN_vkCmdSetDepthTestEnableEXT vkCmdSetDepthTestEnableEXT;
|
||||||
|
PFN_vkCmdSetDepthWriteEnableEXT vkCmdSetDepthWriteEnableEXT;
|
||||||
|
PFN_vkCmdSetFrontFaceEXT vkCmdSetFrontFaceEXT;
|
||||||
|
PFN_vkCmdSetPrimitiveTopologyEXT vkCmdSetPrimitiveTopologyEXT;
|
||||||
|
PFN_vkCmdSetStencilOpEXT vkCmdSetStencilOpEXT;
|
||||||
|
PFN_vkCmdSetStencilTestEnableEXT vkCmdSetStencilTestEnableEXT;
|
||||||
PFN_vkCreateBuffer vkCreateBuffer;
|
PFN_vkCreateBuffer vkCreateBuffer;
|
||||||
PFN_vkCreateBufferView vkCreateBufferView;
|
PFN_vkCreateBufferView vkCreateBufferView;
|
||||||
PFN_vkCreateCommandPool vkCreateCommandPool;
|
PFN_vkCreateCommandPool vkCreateCommandPool;
|
||||||
|
@ -969,6 +979,50 @@ public:
|
||||||
buffer_barriers.data(), image_barriers.size(), image_barriers.data());
|
buffer_barriers.data(), image_barriers.size(), image_barriers.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BindVertexBuffers2EXT(u32 first_binding, u32 binding_count, const VkBuffer* buffers,
|
||||||
|
const VkDeviceSize* offsets, const VkDeviceSize* sizes,
|
||||||
|
const VkDeviceSize* strides) const noexcept {
|
||||||
|
dld->vkCmdBindVertexBuffers2EXT(handle, first_binding, binding_count, buffers, offsets,
|
||||||
|
sizes, strides);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetCullModeEXT(VkCullModeFlags cull_mode) const noexcept {
|
||||||
|
dld->vkCmdSetCullModeEXT(handle, cull_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDepthBoundsTestEnableEXT(bool enable) const noexcept {
|
||||||
|
dld->vkCmdSetDepthBoundsTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDepthCompareOpEXT(VkCompareOp compare_op) const noexcept {
|
||||||
|
dld->vkCmdSetDepthCompareOpEXT(handle, compare_op);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDepthTestEnableEXT(bool enable) const noexcept {
|
||||||
|
dld->vkCmdSetDepthTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetDepthWriteEnableEXT(bool enable) const noexcept {
|
||||||
|
dld->vkCmdSetDepthWriteEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetFrontFaceEXT(VkFrontFace front_face) const noexcept {
|
||||||
|
dld->vkCmdSetFrontFaceEXT(handle, front_face);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPrimitiveTopologyEXT(VkPrimitiveTopology primitive_topology) const noexcept {
|
||||||
|
dld->vkCmdSetPrimitiveTopologyEXT(handle, primitive_topology);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStencilOpEXT(VkStencilFaceFlags face_mask, VkStencilOp fail_op, VkStencilOp pass_op,
|
||||||
|
VkStencilOp depth_fail_op, VkCompareOp compare_op) const noexcept {
|
||||||
|
dld->vkCmdSetStencilOpEXT(handle, face_mask, fail_op, pass_op, depth_fail_op, compare_op);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStencilTestEnableEXT(bool enable) const noexcept {
|
||||||
|
dld->vkCmdSetStencilTestEnableEXT(handle, enable ? VK_TRUE : VK_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
|
void BindTransformFeedbackBuffersEXT(u32 first, u32 count, const VkBuffer* buffers,
|
||||||
const VkDeviceSize* offsets,
|
const VkDeviceSize* offsets,
|
||||||
const VkDeviceSize* sizes) const noexcept {
|
const VkDeviceSize* sizes) const noexcept {
|
||||||
|
|
|
@ -212,7 +212,7 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
|
||||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||||
// clang-format off
|
// clang-format off
|
||||||
const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
|
const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
|
||||||
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Capture Screenshot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+P"), Qt::WidgetWithChildrenShortcut}},
|
||||||
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Change Docked Mode"), QStringLiteral("Main Window"), {QStringLiteral("F10"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
|
{QStringLiteral("Continue/Pause Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F4"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Decrease Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("-"), Qt::ApplicationShortcut}},
|
||||||
|
@ -220,8 +220,8 @@ const std::array<UISettings::Shortcut, 16> Config::default_hotkeys{{
|
||||||
{QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
|
{QStringLiteral("Exit yuzu"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Q"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
|
{QStringLiteral("Fullscreen"), QStringLiteral("Main Window"), {QStringLiteral("F11"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Increase Speed Limit"), QStringLiteral("Main Window"), {QStringLiteral("+"), Qt::ApplicationShortcut}},
|
||||||
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::ApplicationShortcut}},
|
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
|
||||||
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WindowShortcut}},
|
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
|
||||||
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
{QStringLiteral("Mute Audio"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
||||||
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
|
{QStringLiteral("Stop Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F5"), Qt::WindowShortcut}},
|
||||||
|
@ -665,11 +665,13 @@ void Config::ReadShortcutValues() {
|
||||||
const auto& [keyseq, context] = shortcut;
|
const auto& [keyseq, context] = shortcut;
|
||||||
qt_config->beginGroup(group);
|
qt_config->beginGroup(group);
|
||||||
qt_config->beginGroup(name);
|
qt_config->beginGroup(name);
|
||||||
|
// No longer using ReadSetting for shortcut.second as it innacurately returns a value of 1
|
||||||
|
// for WidgetWithChildrenShortcut which is a value of 3. Needed to fix shortcuts the open
|
||||||
|
// a file dialog in windowed mode
|
||||||
UISettings::values.shortcuts.push_back(
|
UISettings::values.shortcuts.push_back(
|
||||||
{name,
|
{name,
|
||||||
group,
|
group,
|
||||||
{ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(),
|
{ReadSetting(QStringLiteral("KeySeq"), keyseq).toString(), shortcut.second}});
|
||||||
ReadSetting(QStringLiteral("Context"), context).toInt()}});
|
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
qt_config->endGroup();
|
qt_config->endGroup();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,9 +28,9 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
|
||||||
|
|
||||||
SetConfiguration();
|
SetConfiguration();
|
||||||
|
|
||||||
connect(ui->api, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this,
|
||||||
[this] { UpdateDeviceComboBox(); });
|
[this] { UpdateDeviceComboBox(); });
|
||||||
connect(ui->device, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this,
|
connect(ui->device, qOverload<int>(&QComboBox::activated), this,
|
||||||
[this](int device) { UpdateDeviceSelection(device); });
|
[this](int device) { UpdateDeviceSelection(device); });
|
||||||
|
|
||||||
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
|
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
|
||||||
|
@ -112,7 +112,7 @@ void ConfigureGraphics::UpdateDeviceComboBox() {
|
||||||
enabled = false;
|
enabled = false;
|
||||||
break;
|
break;
|
||||||
case Settings::RendererBackend::Vulkan:
|
case Settings::RendererBackend::Vulkan:
|
||||||
for (const auto device : vulkan_devices) {
|
for (const auto& device : vulkan_devices) {
|
||||||
ui->device->addItem(device);
|
ui->device->addItem(device);
|
||||||
}
|
}
|
||||||
ui->device->setCurrentIndex(vulkan_device);
|
ui->device->setCurrentIndex(vulkan_device);
|
||||||
|
|
|
@ -753,7 +753,7 @@ void GMainWindow::InitializeHotkeys() {
|
||||||
});
|
});
|
||||||
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
|
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Capture Screenshot"), this),
|
||||||
&QShortcut::activated, this, [&] {
|
&QShortcut::activated, this, [&] {
|
||||||
if (emu_thread->IsRunning()) {
|
if (emu_thread != nullptr && emu_thread->IsRunning()) {
|
||||||
OnCaptureScreenshot();
|
OnCaptureScreenshot();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Reference in New Issue