Refactor Display Services and GPU

This commit addresses the incorrect hierarchy of the GPU and refactors them at the same time. Now, the hierarchy much closely matches HOS. This commit also introduces a texture classes, albeit they're not complete and only partially implemented.
This commit is contained in:
◱ PixelyIon
2020-03-25 01:47:31 +05:30
committed by ◱ PixelyIon
parent 500b49d329
commit 42d982c6fb
47 changed files with 1424 additions and 1047 deletions

View File

@ -1,183 +0,0 @@
#pragma once
#include <common.h>
#include <kernel/ipc.h>
#define NFUNC(function) std::bind(&function, this, std::placeholders::_1)
namespace skyline::gpu::device {
/**
* @brief An enumeration of all the devices that can be opened by nvdrv
*/
enum class NvDeviceType {
nvhost_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl
nvhost_gpu, //!< https://switchbrew.org/wiki/NV_services#Channels
nvhost_nvdec, //!< https://switchbrew.org/wiki/NV_services#Channels
nvhost_vic, //!< https://switchbrew.org/wiki/NV_services#Channels
nvmap, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvmap
nvdisp_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-ctrl
nvdisp_disp0, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-disp0.2C_.2Fdev.2Fnvdisp-disp1
nvdisp_disp1, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdisp-disp0.2C_.2Fdev.2Fnvdisp-disp1
nvcec_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvcec-ctrl
nvhdcp_up_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhdcp_up-ctrl
nvdcutil_disp0, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdcutil-disp0.2C_.2Fdev.2Fnvdcutil-disp1
nvdcutil_disp1, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvdcutil-disp0.2C_.2Fdev.2Fnvdcutil-disp1
nvsched_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvsched-ctrl
nverpt_ctrl, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnverpt-ctrl
nvhost_as_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu
nvhost_dbg_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-dbg-gpu
nvhost_prof_gpu, //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-prof-gpu
nvhost_ctrl_gpu //!< https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu
};
/**
* @brief A mapping from a device's path to it's nvDevice entry
*/
const static std::unordered_map<std::string, NvDeviceType> nvDeviceMap{
{"/dev/nvhost-ctrl", NvDeviceType::nvhost_ctrl},
{"/dev/nvhost-gpu", NvDeviceType::nvhost_gpu},
{"/dev/nvhost-nvdec", NvDeviceType::nvhost_nvdec},
{"/dev/nvhost-vic", NvDeviceType::nvhost_vic},
{"/dev/nvmap", NvDeviceType::nvmap},
{"/dev/nvdisp-ctrl", NvDeviceType::nvdisp_ctrl},
{"/dev/nvdisp-disp0", NvDeviceType::nvdisp_disp0},
{"/dev/nvdisp-disp1", NvDeviceType::nvdisp_disp1},
{"/dev/nvcec-ctrl", NvDeviceType::nvcec_ctrl},
{"/dev/nvhdcp_up-ctrl", NvDeviceType::nvhdcp_up_ctrl},
{"/dev/nvdcutil-disp0", NvDeviceType::nvdcutil_disp0},
{"/dev/nvdcutil-disp1", NvDeviceType::nvdcutil_disp1},
{"/dev/nvsched-ctrl", NvDeviceType::nvsched_ctrl},
{"/dev/nverpt-ctrl", NvDeviceType::nverpt_ctrl},
{"/dev/nvhost-as-gpu", NvDeviceType::nvhost_as_gpu},
{"/dev/nvhost-dbg-gpu", NvDeviceType::nvhost_dbg_gpu},
{"/dev/nvhost-prof-gpu", NvDeviceType::nvhost_prof_gpu},
{"/dev/nvhost-ctrl-gpu", NvDeviceType::nvhost_ctrl_gpu}
};
/**
* @brief This enumerates all the possible error codes returned by the Nvidia driver (https://switchbrew.org/wiki/NV_services#Errors)
*/
enum NvStatus : u32 {
Success = 0x0, //!< The operation has succeeded
NotImplemented = 0x1, //!< The operation is not implemented
NotSupported = 0x2, //!< The operation is not supported
NotInitialized = 0x3, //!< The operation uses an uninitialized object
BadParameter = 0x4, //!< The operation was provided a bad parameter
Timeout = 0x5, //!< The operation has timed out
InsufficientMemory = 0x6, //!< The device ran out of memory during the operation
ReadOnlyAttribute = 0x7, //!< The mutating operation was performed on a read only section
InvalidState = 0x8, //!< The state of the device was invalid
InvalidAddress = 0x9, //!< The provided address is invalid
InvalidSize = 0xA, //!< The provided size is invalid
BadValue = 0xB, //!< The operation was provided a bad value
AlreadyAllocated = 0xD, //!< An object was tried to be reallocated
Busy = 0xE, //!< The device is busy
ResourceError = 0xF, //!< There was an error accessing the resource
CountMismatch = 0x10, //!< ?
SharedMemoryTooSmall = 0x1000, //!< The shared memory segment is too small
FileOperationFailed = 0x30003, //!< The file operation has failed
DirOperationFailed = 0x30004, //!< The directory operation has failed
IoctlFailed = 0x3000F, //!< The IOCTL operation has failed
AccessDenied = 0x30010, //!< The access to a resource was denied
FileNotFound = 0x30013, //!< A file was not found
ModuleNotPresent = 0xA000E, //!< A module was not present
};
/**
* @brief This holds all the input and output data for an IOCTL function
*/
struct IoctlData {
std::vector<kernel::ipc::InputBuffer> input; //!< A vector of all input IOCTL buffers
std::vector<kernel::ipc::OutputBuffer> output; //!< A vector of all output IOCTL buffers
NvStatus status{NvStatus::Success}; //!< The error code that is returned to the application
/**
* @brief This constructor takes 1 input buffer and 1 output buffer, it's used for Ioctl
* @param input An input buffer
* @param output An output buffer
*/
IoctlData(kernel::ipc::InputBuffer input, kernel::ipc::OutputBuffer output) : input({input}), output({output}) {}
/**
* @brief This constructor takes 1 input buffer, it's used for Ioctl sometimes
* @param output An output buffer
*/
IoctlData(kernel::ipc::InputBuffer input) : input({input}) {}
/**
* @brief This constructor takes 1 output buffer, it's used for Ioctl sometimes
* @param output An output buffer
*/
IoctlData(kernel::ipc::OutputBuffer output) : output({output}) {}
/**
* @brief This constructor takes 2 input buffers and 1 output buffer, it's used for Ioctl1
* @param input1 The first input buffer
* @param input2 The second input buffer
* @param output An output buffer
*/
IoctlData(kernel::ipc::InputBuffer input1, kernel::ipc::InputBuffer input2, kernel::ipc::OutputBuffer output) : input({input1, input2}), output({output}) {}
/**
* @brief This constructor takes 1 input buffer and 2 output buffers, it's used for Ioctl2
* @param input An input buffer
* @param output1 The first output buffer
* @param output2 The second output buffer
*/
IoctlData(kernel::ipc::InputBuffer input, kernel::ipc::OutputBuffer output1, kernel::ipc::OutputBuffer output2) : input({input}), output({output1, output2}) {}
};
/**
* @brief NvDevice is the base class all /dev/nv* devices inherit from
*/
class NvDevice {
protected:
const DeviceState &state; //!< The state of the device
std::unordered_map<u32, std::function<void(IoctlData &)>> vTable; //!< This holds the mapping from an Ioctl to the actual function
public:
u16 refCount{1}; //!< The amount of handles to the device
NvDeviceType deviceType; //!< The type of the device
/**
* @param state The state of the device
* @param deviceType The type of the device
* @param vTable The functions in this device
*/
NvDevice(const DeviceState &state, NvDeviceType deviceType, std::unordered_map<u32, std::function<void(IoctlData &)>> vTable) : state(state), deviceType(deviceType), vTable(vTable) {}
/**
* @brief This returns the name of the current service
* @note It may not return the exact name the service was initialized with if there are multiple entries in ServiceString
* @return The name of the service
*/
std::string getName() {
std::string serviceName;
for (const auto&[name, type] : nvDeviceMap)
if (type == deviceType)
serviceName = name;
return serviceName;
}
/**
* @brief This handles IOCTL calls for devices
* @param cmd The IOCTL command that was called
* @param input The input to the IOCTL call
*/
void HandleIoctl(u32 cmd, IoctlData &input) {
std::function<void(IoctlData &)> function;
try {
function = vTable.at(cmd);
} catch (std::out_of_range &) {
state.logger->Warn("Cannot find IOCTL for device '{}': 0x{:X}", getName(), cmd);
input.status = NvStatus::NotImplemented;
return;
}
try {
function(input);
} catch (std::exception &e) {
throw exception("{} (Device: {})", e.what(), getName());
}
}
};
}

View File

@ -1,5 +0,0 @@
#include "nvhost_as_gpu.h"
namespace skyline::gpu::device {
NvHostAsGpu::NvHostAsGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_as_gpu, {}) {}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostAsGpu (/dev/nvhost-as-gpu) is used to access GPU virtual address spaces (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-as-gpu)
*/
class NvHostAsGpu : public NvDevice {
public:
NvHostAsGpu(const DeviceState &state);
};
}

View File

@ -1,42 +0,0 @@
#include "nvhost_channel.h"
#include <kernel/types/KProcess.h>
namespace skyline::gpu::device {
NvHostChannel::NvHostChannel(const DeviceState &state, NvDeviceType type) : NvDevice(state, type, {
{0x40044801, NFUNC(NvHostChannel::SetNvmapFd)},
{0xC0104809, NFUNC(NvHostChannel::AllocObjCtx)},
{0xC010480B, NFUNC(NvHostChannel::ZcullBind)},
{0xC018480C, NFUNC(NvHostChannel::SetErrorNotifier)},
{0x4004480D, NFUNC(NvHostChannel::SetPriority)},
{0xC020481A, NFUNC(NvHostChannel::AllocGpfifoEx2)},
{0x40084714, NFUNC(NvHostChannel::SetUserData)}
}) {}
void NvHostChannel::SetNvmapFd(skyline::gpu::device::IoctlData &buffer) {}
void NvHostChannel::AllocObjCtx(skyline::gpu::device::IoctlData &buffer) {}
void NvHostChannel::ZcullBind(IoctlData &buffer) {}
void NvHostChannel::SetErrorNotifier(skyline::gpu::device::IoctlData &buffer) {}
void NvHostChannel::SetPriority(skyline::gpu::device::IoctlData &buffer) {
auto priority = state.process->GetObject<NvChannelPriority>(buffer.input[0].address);
switch (priority) {
case NvChannelPriority::Low:
timeslice = 1300;
break;
case NvChannelPriority::Medium:
timeslice = 2600;
break;
case NvChannelPriority::High:
timeslice = 5200;
break;
}
}
void NvHostChannel::AllocGpfifoEx2(skyline::gpu::device::IoctlData &buffer) {}
void NvHostChannel::SetUserData(skyline::gpu::device::IoctlData &buffer) {}
}

View File

@ -1,57 +0,0 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostChannel is used as a common interface for all Channel devices (https://switchbrew.org/wiki/NV_services#Channels)
*/
class NvHostChannel : public NvDevice {
private:
enum class NvChannelPriority : u32 {
Low = 0x32,
Medium = 0x64,
High = 0x94
};
u32 timeslice{};
public:
NvHostChannel(const DeviceState &state, NvDeviceType type);
/**
* @brief This sets the nvmap file descriptor (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_NVMAP_FD)
*/
void SetNvmapFd(IoctlData &buffer);
/**
* @brief This allocates a graphic context object (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_OBJ_CTX)
*/
void AllocObjCtx(IoctlData &buffer);
/**
* @brief This initializes the error notifier for this channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ZCULL_BIND)
*/
void ZcullBind(IoctlData &buffer);
/**
* @brief This initializes the error notifier for this channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_ERROR_NOTIFIER)
*/
void SetErrorNotifier(IoctlData &buffer);
/**
* @brief This sets the priority of the channel (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_PRIORITY)
*/
void SetPriority(IoctlData &buffer);
/**
* @brief This allocates a GPFIFO entry (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_ALLOC_GPFIFO_EX2)
*/
void AllocGpfifoEx2(IoctlData &buffer);
/**
* @brief This sets the user specific data (https://switchbrew.org/wiki/NV_services#NVGPU_IOCTL_CHANNEL_SET_USER_DATA)
*/
void SetUserData(IoctlData &buffer);
};
}

View File

@ -1,5 +0,0 @@
#include "nvhost_ctrl.h"
namespace skyline::gpu::device {
NvHostCtrl::NvHostCtrl(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl, {}) {}
}

View File

@ -1,13 +0,0 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostCtrl (/dev/nvhost-ctrl) is used for GPU synchronization (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl)
*/
class NvHostCtrl : public NvDevice {
public:
NvHostCtrl(const DeviceState &state);
};
}

View File

@ -1,138 +0,0 @@
#include "nvhost_ctrl_gpu.h"
#include <kernel/types/KProcess.h>
namespace skyline::gpu::device {
NvHostCtrlGpu::NvHostCtrlGpu(const DeviceState &state) : NvDevice(state, NvDeviceType::nvhost_ctrl_gpu, {
{0x80044701, NFUNC(NvHostCtrlGpu::ZCullGetCtxSize)},
{0x80284702, NFUNC(NvHostCtrlGpu::ZCullGetInfo)},
{0xC0184706, NFUNC(NvHostCtrlGpu::GetTpcMasks)},
{0xC0B04705, NFUNC(NvHostCtrlGpu::GetCharacteristics)},
{0x80084714, NFUNC(NvHostCtrlGpu::GetActiveSlotMask)}
}) {}
void NvHostCtrlGpu::ZCullGetCtxSize(IoctlData &buffer) {
u32 size = 0x1;
state.process->WriteMemory(size, buffer.output[0].address);
}
void NvHostCtrlGpu::ZCullGetInfo(skyline::gpu::device::IoctlData &buffer) {
struct {
u32 widthAlignPixels{0x20};
u32 heightAlignPixels{0x20};
u32 pixelSquaresByAliquots{0x400};
u32 aliquotTotal{0x800};
u32 regionByteMultiplier{0x20};
u32 regionHeaderSize{0x20};
u32 subregionHeaderSize{0xC0};
u32 subregionWidthAlignPixels{0x20};
u32 subregionHeightAlignPixels{0x40};
u32 subregionCount{0x10};
} zCullInfo;
state.process->WriteMemory(zCullInfo, buffer.output[0].address);
}
void NvHostCtrlGpu::GetCharacteristics(IoctlData &buffer) {
struct GpuCharacteristics {
u32 arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
u32 impl; // 0xB (NVGPU_GPU_IMPL_GM20B) or 0xE (NVGPU_GPU_IMPL_GM20B_B)
u32 rev; // 0xA1 (Revision A1)
u32 numGpc; // 0x1
u64 l2CacheSize; // 0x40000
u64 onBoardVideoMemorySize; // 0x0 (not used)
u32 numTpcPerGpc; // 0x2
u32 busType; // 0x20 (NVGPU_GPU_BUS_TYPE_AXI)
u32 bigPageSize; // 0x20000
u32 compressionPageSize; // 0x20000
u32 pdeCoverageBitCount; // 0x1B
u32 availableBigPageSizes; // 0x30000
u32 gpcMask; // 0x1
u32 smArchSmVersion; // 0x503 (Maxwell Generation 5.0.3)
u32 smArchSpaVersion; // 0x503 (Maxwell Generation 5.0.3)
u32 smArchWarpCount; // 0x80
u32 gpuVaBitCount; // 0x28
u32 reserved; // NULL
u64 flags; // 0x55 (HAS_SYNCPOINTS | SUPPORT_SPARSE_ALLOCS | SUPPORT_CYCLE_STATS | SUPPORT_CYCLE_STATS_SNAPSHOT)
u32 twodClass; // 0x902D (FERMI_TWOD_A)
u32 threedClass; // 0xB197 (MAXWELL_B)
u32 computeClass; // 0xB1C0 (MAXWELL_COMPUTE_B)
u32 gpfifoClass; // 0xB06F (MAXWELL_CHANNEL_GPFIFO_A)
u32 inlineToMemoryClass; // 0xA140 (KEPLER_INLINE_TO_MEMORY_B)
u32 dmaCopyClass; // 0xB0B5 (MAXWELL_DMA_COPY_A)
u32 maxFbpsCount; // 0x1
u32 fbpEnMask; // 0x0 (disabled)
u32 maxLtcPerFbp; // 0x2
u32 maxLtsPerLtc; // 0x1
u32 maxTexPerTpc; // 0x0 (not supported)
u32 maxGpcCount; // 0x1
u32 ropL2EnMask0; // 0x21D70 (fuse_status_opt_rop_l2_fbp_r)
u32 ropL2EnMask1; // 0x0
u64 chipName; // 0x6230326D67 ("gm20b")
u64 grCompbitStoreBaseHw; // 0x0 (not supported)
};
struct Data {
u64 gpuCharacteristicsBufSize; // InOut
u64 gpuCharacteristicsBufAddr; // In
GpuCharacteristics gpuCharacteristics; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address);
data.gpuCharacteristics = {
.arch = 0x120,
.impl = 0xB,
.rev = 0xA1,
.numGpc = 0x1,
.l2CacheSize = 0x40000,
.onBoardVideoMemorySize = 0x0,
.numTpcPerGpc = 0x2,
.busType = 0x20,
.bigPageSize = 0x20000,
.compressionPageSize = 0x20000,
.pdeCoverageBitCount = 0x1B,
.availableBigPageSizes = 0x30000,
.gpcMask = 0x1,
.smArchSmVersion = 0x503,
.smArchSpaVersion = 0x503,
.smArchWarpCount = 0x80,
.gpuVaBitCount = 0x2,
.flags = 0x55,
.twodClass = 0x902D,
.threedClass = 0xB197,
.computeClass = 0xB1C0,
.gpfifoClass = 0xB06F,
.inlineToMemoryClass = 0xA140,
.dmaCopyClass = 0xB0B5,
.maxFbpsCount = 0x1,
.fbpEnMask = 0x0,
.maxLtcPerFbp = 0x2,
.maxLtsPerLtc = 0x1,
.maxTexPerTpc = 0x0,
.maxGpcCount = 0x1,
.ropL2EnMask0 = 0x21D70,
.ropL2EnMask1 = 0x0,
.chipName = 0x6230326D67,
.grCompbitStoreBaseHw = 0x0
};
data.gpuCharacteristicsBufSize = 0xA0;
state.process->WriteMemory(data, buffer.output[0].address);
}
void NvHostCtrlGpu::GetTpcMasks(IoctlData &buffer) {
struct Data {
u32 maskBufSize; // In
u32 reserved[3]; // In
u64 maskBuf; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address);
if (data.maskBufSize)
data.maskBuf = 0x3;
state.process->WriteMemory(data, buffer.output[0].address);
}
void NvHostCtrlGpu::GetActiveSlotMask(IoctlData &buffer) {
struct Data {
u32 slot; // Out
u32 mask; // Out
} data = {
.slot = 0x07,
.mask = 0x01
};
state.process->WriteMemory(data, buffer.output[0].address);
}
}

View File

@ -1,38 +0,0 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvHostCtrlGpu (/dev/nvhost-ctrl-gpu) is used for context independent operations on the underlying GPU (https://switchbrew.org/wiki/NV_services#.2Fdev.2Fnvhost-ctrl-gpu)
*/
class NvHostCtrlGpu : public NvDevice {
public:
NvHostCtrlGpu(const DeviceState &state);
/**
* @brief This returns a u32 GPU ZCULL Context Size (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_CTX_SIZE)
*/
void ZCullGetCtxSize(IoctlData &buffer);
/**
* @brief This returns a the GPU ZCULL Information (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZCULL_GET_INFO)
*/
void ZCullGetInfo(IoctlData &buffer);
/**
* @brief This returns a struct with certain GPU characteristics (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_CHARACTERISTICS)
*/
void GetCharacteristics(IoctlData &buffer);
/**
* @brief This returns the TPC mask value for each GPC (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_GET_TPC_MASKS)
*/
void GetTpcMasks(IoctlData &buffer);
/**
* @brief This returns the mask value for a ZBC slot (https://switchbrew.org/wiki/NV_services#NVGPU_GPU_IOCTL_ZBC_GET_ACTIVE_SLOT_MASK)
*/
void GetActiveSlotMask(IoctlData &buffer);
};
}

View File

@ -1,140 +0,0 @@
#include "nvmap.h"
#include <kernel/types/KProcess.h>
namespace skyline::gpu::device {
NvMap::NvMapObject::NvMapObject(u32 id, u32 size) : id(id), size(size) {}
NvMap::NvMap(const DeviceState &state) : NvDevice(state, NvDeviceType::nvmap, {
{0xC0080101, NFUNC(NvMap::Create)},
{0xC0080103, NFUNC(NvMap::FromId)},
{0xC0200104, NFUNC(NvMap::Alloc)},
{0xC0180105, NFUNC(NvMap::Free)},
{0xC00C0109, NFUNC(NvMap::Param)},
{0xC008010E, NFUNC(NvMap::GetId)}
}) {}
void NvMap::Create(IoctlData &buffer) {
struct Data {
u32 size; // In
u32 handle; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address);
handleTable[handleIndex] = std::make_shared<NvMapObject>(idIndex++, data.size);
data.handle = handleIndex++;
state.process->WriteMemory(data, buffer.output[0].address);
state.logger->Debug("Create: Input: Size: 0x{:X}, Output: Handle: 0x{:X}, Status: {}", data.size, data.handle, buffer.status);
}
void NvMap::FromId(skyline::gpu::device::IoctlData &buffer) {
struct Data {
u32 id; // In
u32 handle; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address);
bool found{};
for (const auto &object : handleTable) {
if (object.second->id == data.id) {
data.handle = object.first;
found = true;
break;
}
}
if (found)
state.process->WriteMemory(data, buffer.output[0].address);
else
buffer.status = NvStatus::BadValue;
state.logger->Debug("FromId: Input: Handle: 0x{:X}, Output: ID: 0x{:X}, Status: {}", data.handle, data.id, buffer.status);
}
void NvMap::Alloc(IoctlData &buffer) {
struct Data {
u32 handle; // In
u32 heapMask; // In
u32 flags; // In
u32 align; // In
u8 kind; // In
u8 _pad0_[7];
u64 address; // InOut
} data = state.process->GetObject<Data>(buffer.input[0].address);
auto &object = handleTable.at(data.handle);
object->heapMask = data.heapMask;
object->flags = data.flags;
object->align = data.align;
object->kind = data.kind;
object->address = data.address;
object->status = NvMapObject::Status::Allocated;
state.logger->Debug("Alloc: Input: Handle: 0x{:X}, HeapMask: 0x{:X}, Flags: {}, Align: 0x{:X}, Kind: {}, Address: 0x{:X}, Output: Status: {}", data.handle, data.heapMask, data.flags, data.align, data.kind, data.address, buffer.status);
}
void NvMap::Free(skyline::gpu::device::IoctlData &buffer) {
struct Data {
u32 handle; // In
u32 _pad0_;
u32 address; // Out
u32 size; // Out
u64 flags; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address);
const auto &object = handleTable.at(data.handle);
if (object.use_count() > 1) {
data.address = static_cast<u32>(object->address);
data.flags = 0x0;
} else {
data.address = 0x0;
data.flags = 0x1; // Not free yet
}
data.size = object->size;
handleTable.erase(data.handle);
state.process->WriteMemory(data, buffer.output[0].address);
}
void NvMap::Param(IoctlData &buffer) {
enum class Parameter : u32 { Size = 1, Alignment = 2, Base = 3, HeapMask = 4, Kind = 5, Compr = 6 }; // https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h#102
struct Data {
u32 handle; // In
Parameter parameter; // In
u32 result; // Out
} data = state.process->GetObject<Data>(buffer.input[0].address);
auto &object = handleTable.at(data.handle);
switch (data.parameter) {
case Parameter::Size:
data.result = object->size;
break;
case Parameter::Alignment:
case Parameter::HeapMask:
case Parameter::Kind: {
if (object->status != NvMapObject::Status::Allocated)
data.result = NvStatus::BadParameter;
switch (data.parameter) {
case Parameter::Alignment:
data.result = object->align;
break;
case Parameter::HeapMask:
data.result = object->heapMask;
break;
case Parameter::Kind:
data.result = object->kind;
break;
default:
break;
}
break;
}
case Parameter::Base:
buffer.status = NvStatus::NotImplemented;
break;
case Parameter::Compr:
buffer.status = NvStatus::NotImplemented;
break;
}
state.process->WriteMemory(data, buffer.output[0].address);
state.logger->Debug("Param: Input: Handle: 0x{:X}, Parameter: {}, Output: Result: 0x{:X}, Status: {}", data.handle, data.parameter, data.result, buffer.status);
}
void NvMap::GetId(skyline::gpu::device::IoctlData &buffer) {
struct Data {
u32 id; // Out
u32 handle; // In
} data = state.process->GetObject<Data>(buffer.input[0].address);
data.id = handleTable.at(data.handle)->id;
state.process->WriteMemory(data, buffer.output[0].address);
state.logger->Debug("GetId: Input: Handle: 0x{:X}, Output: ID: 0x{:X}, Status: {}", data.handle, data.id, buffer.status);
}
}

View File

@ -1,71 +0,0 @@
#pragma once
#include "nvdevice.h"
namespace skyline::gpu::device {
/**
* @brief NvMap (/dev/nvmap) is used to map certain CPU memory as GPU memory (https://switchbrew.org/wiki/NV_services) (https://android.googlesource.com/kernel/tegra/+/refs/heads/android-tegra-flounder-3.10-marshmallow/include/linux/nvmap.h)
*/
class NvMap : public NvDevice {
public:
/**
* @brief NvMapObject is used to hold the state of held objects
*/
struct NvMapObject {
u32 id; //!< The ID of this object
u32 size; //!< The size of this object
u64 address{}; //!< The address of the allocation
u32 flags{}; //!< The flag of the memory (0 = Read Only, 1 = Read-Write)
u32 align{}; //!< The alignment of the allocation
u32 heapMask{}; //!< This is set during Alloc and returned during Param
u8 kind{}; //!< This is same as heapMask
enum class Status {
Created, //!< The object has been created but memory has not been allocated
Allocated //!< The object has been allocated
} status{Status::Created}; //!< This holds the status of the object
/**
* @param handle The ID of this object
* @param size The size of the object in bytes
*/
NvMapObject(u32 id, u32 size);
};
std::unordered_map<handle_t, std::shared_ptr<NvMapObject>> handleTable; //!< A mapping from a handle to it's corresponding NvMapObject
handle_t handleIndex{1}; //!< This is used to keep track of the next handle to allocate
u32 idIndex{1}; //!< This is used to keep track of the next ID to allocate
NvMap(const DeviceState &state);
/**
* @brief This creates an NvMapObject and returns an handle to it (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_CREATE)
*/
void Create(IoctlData &buffer);
/**
* @brief This returns the handle of an NvMapObject from it's ID (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FROM_ID)
*/
void FromId(IoctlData &buffer);
/**
* @brief This allocates memory for an NvMapObject (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_ALLOC)
*/
void Alloc(IoctlData &buffer);
/**
* @brief This frees previously allocated memory (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_FREE)
*/
void Free(IoctlData &buffer);
/**
* @brief This returns a particular parameter from an NvMapObject (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_PARAM)
*/
void Param(IoctlData &buffer);
/**
* @brief This returns the ID of an NvMapObject from it's handle (https://switchbrew.org/wiki/NV_services#NVMAP_IOC_GET_ID)
*/
void GetId(IoctlData &buffer);
};
}

View File

@ -1,142 +0,0 @@
#include "display.h"
#include <kernel/types/KProcess.h>
#include <gpu.h>
namespace skyline::gpu {
Buffer::Buffer(const DeviceState &state, u32 slot, GbpBuffer &gbpBuffer) : state(state), slot(slot), gbpBuffer(gbpBuffer), resolution{gbpBuffer.width, gbpBuffer.height} {
if (gbpBuffer.nvmapHandle)
nvBuffer = state.gpu->GetDevice<device::NvMap>(device::NvDeviceType::nvmap)->handleTable.at(gbpBuffer.nvmapHandle);
else {
auto nvmap = state.gpu->GetDevice<device::NvMap>(device::NvDeviceType::nvmap);
for (const auto &object : nvmap->handleTable) {
if (object.second->id == gbpBuffer.nvmapId) {
nvBuffer = object.second;
break;
}
}
if (!nvBuffer)
throw exception("A QueueBuffer request has an invalid NVMap Handle ({}) and ID ({})", gbpBuffer.nvmapHandle, gbpBuffer.nvmapId);
}
switch (gbpBuffer.format) {
case WINDOW_FORMAT_RGBA_8888:
case WINDOW_FORMAT_RGBX_8888:
bpp = sizeof(u32);
break;
case WINDOW_FORMAT_RGB_565:
bpp = sizeof(u16);
break;
default:
throw exception("Unknown pixel format used for FB");
}
}
u8 *Buffer::GetAddress() {
return state.process->GetPointer<u8>(nvBuffer->address + gbpBuffer.offset);
}
BufferQueue::BufferQueue(const DeviceState &state) : state(state) {}
void BufferQueue::RequestBuffer(Parcel &in, Parcel &out) {
u32 slot = *reinterpret_cast<u32 *>(in.data.data() + constant::TokenLength);
out.WriteData<u32>(1);
out.WriteData<u32>(sizeof(GbpBuffer));
out.WriteData<u32>(0);
out.WriteData(queue.at(slot)->gbpBuffer);
state.logger->Debug("RequestBuffer: Slot: {}", slot, sizeof(GbpBuffer));
}
void BufferQueue::DequeueBuffer(Parcel &in, Parcel &out) {
struct Data {
u32 format;
u32 width;
u32 height;
u32 timestamps;
u32 usage;
} *data = reinterpret_cast<Data *>(in.data.data() + constant::TokenLength);
i64 slot{-1};
while (slot == -1) {
for (auto &buffer : queue) {
if (buffer.second->status == BufferStatus::Free && buffer.second->resolution.width == data->width && buffer.second->resolution.height == data->height && buffer.second->gbpBuffer.usage == data->usage) {
slot = buffer.first;
buffer.second->status = BufferStatus::Dequeued;
break;
}
}
asm("yield");
}
struct {
u32 slot;
u32 _unk_[13];
} output{
.slot = static_cast<u32>(slot)
};
out.WriteData(output);
state.logger->Debug("DequeueBuffer: Width: {}, Height: {}, Format: {}, Usage: {}, Timestamps: {}, Slot: {}", data->width, data->height, data->format, data->usage, data->timestamps, slot);
}
void BufferQueue::QueueBuffer(Parcel &in, Parcel &out) {
struct Data {
u32 slot;
u64 timestamp;
u32 autoTimestamp;
ARect crop;
u32 scalingMode;
u32 transform;
u32 stickyTransform;
u64 _unk0_;
u32 swapInterval;
Fence fence[4];
} *data = reinterpret_cast<Data *>(in.data.data() + constant::TokenLength);
auto buffer = queue.at(data->slot);
buffer->status = BufferStatus::Queued;
displayQueue.emplace(buffer);
state.gpu->bufferEvent->Signal();
struct {
u32 width;
u32 height;
u32 _pad0_[3];
} output{
.width = buffer->gbpBuffer.width,
.height = buffer->gbpBuffer.height
};
out.WriteData(output);
state.logger->Debug("QueueBuffer: Timestamp: {}, Auto Timestamp: {}, Crop: [T: {}, B: {}, L: {}, R: {}], Scaling Mode: {}, Transform: {}, Sticky Transform: {}, Swap Interval: {}, Slot: {}", data->timestamp, data->autoTimestamp, data->crop.top, data->crop.bottom, data->crop.left, data->crop.right, data->scalingMode, data->transform, data->stickyTransform, data->swapInterval, data->slot);
}
void BufferQueue::CancelBuffer(Parcel &parcel) {
struct Data {
u32 slot;
Fence fence[4];
} *data = reinterpret_cast<Data *>(parcel.data.data() + constant::TokenLength);
FreeBuffer(data->slot);
state.logger->Debug("CancelBuffer: Slot: {}", data->slot);
}
void BufferQueue::SetPreallocatedBuffer(Parcel &parcel) {
auto pointer = parcel.data.data() + constant::TokenLength;
struct Data {
u32 slot;
u32 _unk0_;
u32 length;
u32 _pad0_;
} *data = reinterpret_cast<Data *>(pointer);
pointer += sizeof(Data);
auto gbpBuffer = reinterpret_cast<GbpBuffer *>(pointer);
queue[data->slot] = std::make_shared<Buffer>(state, data->slot, *gbpBuffer);
state.gpu->bufferEvent->Signal();
state.logger->Debug("SetPreallocatedBuffer: Slot: {}, Magic: 0x{:X}, Width: {}, Height: {}, Stride: {}, Format: {}, Usage: {}, Index: {}, ID: {}, Handle: {}, Offset: 0x{:X}, Block Height: {}, Size: 0x{:X}",
data->slot,
gbpBuffer->magic,
gbpBuffer->width,
gbpBuffer->height,
gbpBuffer->stride,
gbpBuffer->format,
gbpBuffer->usage,
gbpBuffer->index,
gbpBuffer->nvmapId,
gbpBuffer->nvmapHandle,
gbpBuffer->offset,
(1U << gbpBuffer->blockHeightLog2),
gbpBuffer->size);
}
}

View File

@ -1,171 +0,0 @@
#pragma once
#include <common.h>
#include <queue>
#include <gpu/devices/nvmap.h>
#include "parcel.h"
namespace skyline::gpu {
/**
* @brief A struct that encapsulates a resolution
*/
struct Resolution {
u32 width; //!< The width component of the resolution
u32 height; //!< The height component of the resolution
inline bool operator==(const Resolution &r) {
return (width == r.width) && (height == r.height);
}
inline bool operator!=(const Resolution &r) {
return !operator==(r);
}
};
/**
* @brief An enumeration of all the possible display IDs (https://switchbrew.org/wiki/Display_services#DisplayName)
*/
enum class DisplayId : u64 {
Default,
External,
Edid,
Internal,
Null
};
/**
* @brief A mapping from a display's name to it's displayType entry
*/
static const std::unordered_map<std::string, DisplayId> displayTypeMap{
{"Default", DisplayId::Default},
{"External", DisplayId::External},
{"Edid", DisplayId::Edid},
{"Internal", DisplayId::Internal},
{"Null", DisplayId::Null},
};
/**
* @brief The status of a specific layer
*/
enum class LayerStatus {
Uninitialized,
Initialized
};
/**
* @brief The status of a specific buffer
*/
enum class BufferStatus {
Free,
Dequeued,
Queued
};
/**
* @brief This struct holds information about the graphics buffer (https://github.com/reswitched/libtransistor/blob/0f0c36227842c344d163922fc98ee76229e9f0ee/lib/display/graphic_buffer_queue.c#L66)
*/
struct GbpBuffer {
u32 magic; //!< The magic of the graphics buffer: 0x47424652
u32 width; //!< The width of the buffer
u32 height; //!< The height of the buffer
u32 stride; //!< The stride of the buffer
u32 format; //!< The format of the buffer, this corresponds to AHardwareBuffer_Format
u32 usage; //!< The usage flags for the buffer
u32 _pad0_;
u32 index; //!< The index of the buffer
u32 _pad1_[3];
u32 nvmapId; //!< The ID of the buffer in regards to /dev/nvmap
u32 _pad2_[8];
u32 size; //!< The size of the buffer
u32 _pad3_[8];
u32 nvmapHandle; //!< The handle of the buffer in regards to /dev/nvmap
u32 offset; //!< This is the offset of the pixel data in the GPU Buffer
u32 _pad4_;
u32 blockHeightLog2; //!< The log2 of the block height
u32 _pad5_[58];
};
/**
* @brief This represents conditions for the completion of an asynchronous graphics operation
*/
struct Fence {
u32 syncptId; //!< The ID of the syncpoint
u32 syncptValue; //!< The value of the syncpoint
};
/**
* @brief This holds the state and describes a single Buffer
*/
class Buffer {
public:
const DeviceState &state; //!< The state of the device
u32 slot; //!< The slot the buffer is in
u32 bpp; //!< The amount of bytes per pixel
Resolution resolution; //!< The resolution of this buffer
GbpBuffer gbpBuffer; //!< The information about the underlying buffer
BufferStatus status{BufferStatus::Free}; //!< The status of this buffer
std::shared_ptr<device::NvMap::NvMapObject> nvBuffer{}; //!< A shared pointer to the buffer's nvmap object
/**
* @param state The state of the device
* @param slot The slot this buffer is in
* @param gpBuffer The GbpBuffer object for this buffer
*/
Buffer(const DeviceState &state, u32 slot, GbpBuffer &gbpBuffer);
/**
* @return The address of the buffer on the kernel
*/
u8* GetAddress();
};
/**
* @brief This is used to manage and queue up all display buffers to be shown
*/
class BufferQueue {
private:
const DeviceState &state; //!< The state of the device
public:
std::unordered_map<u32, std::shared_ptr<Buffer>> queue; //!< A vector of shared pointers to all the queued buffers
std::queue<std::shared_ptr<Buffer>> displayQueue; //!< A queue of all the buffers to be posted to the display
/**
* @param state The state of the device
*/
BufferQueue(const DeviceState &state);
/**
* @brief This the GbpBuffer struct of the specified buffer
*/
void RequestBuffer(Parcel &in, Parcel &out);
/**
* @brief This returns the slot of a free buffer
*/
void DequeueBuffer(Parcel &in, Parcel &out);
/**
* @brief This queues a buffer to be displayed
*/
void QueueBuffer(Parcel &in, Parcel &out);
/**
* @brief This removes a previously queued buffer
*/
void CancelBuffer(Parcel &parcel);
/**
* @brief This adds a pre-existing buffer to the queue
*/
void SetPreallocatedBuffer(Parcel &parcel);
/**
* @brief This frees a buffer which is currently queued
* @param slotNo The slot of the buffer
*/
inline void FreeBuffer(u32 slotNo) {
queue.at(slotNo)->status = BufferStatus::Free;
}
};
}

View File

@ -1,37 +0,0 @@
#include "parcel.h"
#include <os.h>
#include <kernel/types/KProcess.h>
namespace skyline::gpu {
Parcel::Parcel(kernel::ipc::InputBuffer &buffer, const DeviceState &state) : Parcel(buffer.address, buffer.size, state) {}
Parcel::Parcel(u64 address, u64 size, const DeviceState &state) : state(state) {
state.process->ReadMemory(&header, address, sizeof(ParcelHeader));
if (size < (sizeof(ParcelHeader) + header.dataSize + header.objectsSize))
throw exception("The size of the parcel according to the header exceeds the specified size");
data.resize(header.dataSize);
state.process->ReadMemory(data.data(), address + header.dataOffset, header.dataSize);
objects.resize(header.objectsSize);
state.process->ReadMemory(objects.data(), address + header.objectsOffset, header.objectsSize);
}
Parcel::Parcel(const DeviceState &state) : state(state) {}
u64 Parcel::WriteParcel(kernel::ipc::OutputBuffer &buffer) {
return WriteParcel(buffer.address, buffer.size);
}
u64 Parcel::WriteParcel(u64 address, u64 maxSize) {
header.dataSize = static_cast<u32>(data.size());
header.dataOffset = sizeof(ParcelHeader);
header.objectsSize = static_cast<u32>(objects.size());
header.objectsOffset = sizeof(ParcelHeader) + data.size();
u64 totalSize = sizeof(ParcelHeader) + header.dataSize + header.objectsSize;
if (maxSize < totalSize)
throw exception("The size of the parcel exceeds maxSize");
state.process->WriteMemory(header, address);
state.process->WriteMemory(data.data(), address + header.dataOffset, data.size());
state.process->WriteMemory(objects.data(), address + header.objectsOffset, objects.size());
return totalSize;
}
}

View File

@ -1,95 +0,0 @@
#pragma once
#include <common.h>
#include <kernel/ipc.h>
namespace skyline::gpu {
/**
* @brief This class encapsulates a Parcel object (https://switchbrew.org/wiki/Display_services#Parcel)
*/
class Parcel {
private:
/**
* @brief This holds the header of a parcel
*/
struct ParcelHeader {
u32 dataSize;
u32 dataOffset;
u32 objectsSize;
u32 objectsOffset;
} header{};
static_assert(sizeof(ParcelHeader) == 0x10);
const DeviceState &state; //!< The state of the device
public:
std::vector<u8> data; //!< A vector filled with data in the parcel
std::vector<u8> objects; //!< A vector filled with objects in the parcel
/**
* @brief This constructor fills in the Parcel object with data from a IPC buffer
* @param buffer The buffer that contains the parcel
* @param state The state of the device
*/
Parcel(kernel::ipc::InputBuffer &buffer, const DeviceState &state);
/**
* @brief This constructor fills in the Parcel object with data from a Parcel on a remote process
* @param address The remote address of the parcel
* @param size The size of the parcel
* @param state The state of the device
*/
Parcel(u64 address, u64 size, const DeviceState &state);
/**
* @brief This constructor is used to create an empty parcel then write to a process
* @param state The state of the device
*/
Parcel(const DeviceState &state);
/**
* @brief Writes some data to the Parcel
* @tparam ValueType The type of the object to write
* @param value The object to be written
*/
template<typename ValueType>
void WriteData(const ValueType &value) {
data.reserve(data.size() + sizeof(ValueType));
auto item = reinterpret_cast<const u8 *>(&value);
for (uint index = 0; sizeof(ValueType) > index; index++) {
data.push_back(*item);
item++;
}
}
/**
* @brief Writes an object to the Parcel
* @tparam ValueType The type of the object to write
* @param value The object to be written
*/
template<typename ValueType>
void WriteObject(const ValueType &value) {
objects.reserve(objects.size() + sizeof(ValueType));
auto item = reinterpret_cast<const u8 *>(&value);
for (uint index = 0; sizeof(ValueType) > index; index++) {
objects.push_back(*item);
item++;
}
}
/**
* @brief Writes the Parcel object into a particular output buffer on a process
* @param buffer The buffer to write into
* @return The total size of the message
*/
u64 WriteParcel(kernel::ipc::OutputBuffer &buffer);
/**
* @brief Writes the Parcel object into the process's memory
* @param address The address to write the Parcel object to
* @param maxSize The maximum size of the Parcel
* @return The total size of the message
*/
u64 WriteParcel(u64 address, u64 maxSize);
};
}

View File

@ -0,0 +1,81 @@
#include <android/native_window.h>
#include <kernel/types/KProcess.h>
#include "texture.h"
namespace skyline::gpu {
GuestTexture::GuestTexture(const DeviceState &state, u64 address, texture::Dimensions dimensions, texture::Format format, texture::TileMode tiling, texture::TileConfig layout) : state(state), address(address), dimensions(dimensions), format(format), tileMode(tiling), tileConfig(layout) {}
Texture::Texture(const DeviceState &state, std::shared_ptr<GuestTexture> guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle) : state(state), guest(guest), dimensions(dimensions), format(format), swizzle(swizzle) {
SynchronizeHost();
}
void Texture::SynchronizeHost() {
auto texture = state.process->GetPointer<u8>(guest->address);
auto size = format.GetSize(dimensions);
backing.resize(size);
auto output = reinterpret_cast<u8 *>(backing.data());
if (guest->tileMode == texture::TileMode::Block) {
// Reference on Block-linear tiling: https://gist.github.com/PixelyIon/d9c35050af0ef5690566ca9f0965bc32
constexpr auto sectorWidth = 16; // The width of a sector in bytes
constexpr auto sectorHeight = 2; // The height of a sector in lines
constexpr auto gobWidth = 64; // The width of a GOB in bytes
constexpr auto gobHeight = 8; // The height of a GOB in lines
auto robHeight = gobHeight * guest->tileConfig.blockHeight; // The height of a single ROB (Row of Blocks) in lines
auto surfaceHeightRobs = utils::AlignUp(dimensions.height / format.blockHeight, robHeight) / robHeight; // The height of the surface in ROBs (Row Of Blocks)
auto robWidthBytes = utils::AlignUp((guest->tileConfig.surfaceWidth / format.blockWidth) * format.bpb, gobWidth); // The width of a ROB in bytes
auto robWidthBlocks = robWidthBytes / gobWidth; // The width of a ROB in blocks (and GOBs because block width == 1 on the Tegra X1)
auto robBytes = robWidthBytes * robHeight; // The size of a ROB in bytes
auto gobYOffset = robWidthBytes * gobHeight; // The offset of the next Y-axis GOB from the current one in linear space
auto inputSector = texture; // The address of the input sector
auto outputRob = output; // The address of the output block
for (u32 rob = 0; rob < surfaceHeightRobs; rob++) { // Every Surface contains `surfaceHeightRobs` ROBs
auto outputBlock = outputRob; // We iterate through a block independently of the ROB
for (u32 block = 0; block < robWidthBlocks; block++) { // Every ROB contains `surfaceWidthBlocks` Blocks
auto outputGob = outputBlock; // We iterate through a GOB independently of the block
for (u32 gobY = 0; gobY < guest->tileConfig.blockHeight; gobY++) { // Every Block contains `blockHeight` Y-axis GOBs
for (u32 index = 0; index < sectorWidth * sectorHeight; index++) { // Every Y-axis GOB contains `sectorWidth * sectorHeight` sectors
const u32 xT = ((index << 3) & 0b10000) | ((index << 1) & 0b100000); // Morton-Swizzle on the X-axis
const u32 yT = ((index >> 1) & 0b110) | (index & 0b1); // Morton-Swizzle on the Y-axis
std::memcpy(outputGob + (yT * robWidthBytes) + xT, inputSector, sectorWidth);
inputSector += sectorWidth; // `sectorWidth` bytes are of sequential image data
}
outputGob += gobYOffset; // Increment the output GOB to the next Y-axis GOB
}
outputBlock += gobWidth; // Increment the output block to the next block (As Block Width = 1 GOB Width)
}
outputRob += robBytes; // Increment the output block to the next ROB
}
} else if (guest->tileMode == texture::TileMode::Pitch) {
auto sizeLine = guest->format.GetSize(dimensions.width, 1); // The size of a single line of pixel data
auto sizeStride = guest->format.GetSize(guest->tileConfig.pitch, 1); // The size of a single stride of pixel data
auto inputLine = texture; // The address of the input line
auto outputLine = output; // The address of the output line
for (auto line = 0; line < dimensions.height; line++) {
std::memcpy(outputLine, inputLine, sizeLine);
inputLine += sizeStride;
outputLine += sizeLine;
}
} else if (guest->tileMode == texture::TileMode::Linear) {
std::memcpy(output, texture, size);
}
}
PresentationTexture::PresentationTexture(const DeviceState &state, const std::shared_ptr<GuestTexture> &guest, const texture::Dimensions &dimensions, const texture::Format &format, const std::function<void()> &releaseCallback) : releaseCallback(releaseCallback), Texture(state, guest, dimensions, format, {}) {}
i32 PresentationTexture::GetAndroidFormat() {
switch (format.vkFormat) {
case vk::Format::eR8G8B8A8Unorm:
return WINDOW_FORMAT_RGBA_8888;
case vk::Format::eR5G6B5UnormPack16:
return WINDOW_FORMAT_RGB_565;
default:
throw exception("GetAndroidFormat: Cannot find corresponding Android surface format");
}
}
}

View File

@ -0,0 +1,266 @@
#pragma once
#include <common.h>
#include <vulkan/vulkan.hpp>
namespace skyline {
namespace service::hosbinder {
class IHOSBinderDriver;
}
namespace gpu {
namespace texture {
/*
* @brief This is used to hold the dimensions of a surface
*/
struct Dimensions {
u32 width; //!< The width of the surface
u32 height; //!< The height of the surface
u32 depth; //!< The depth of the surface
constexpr Dimensions() : width(0), height(0), depth(0) {}
constexpr Dimensions(u32 width, u32 height) : width(width), height(height), depth(1) {}
constexpr Dimensions(u32 width, u32 height, u32 depth) : width(width), height(height), depth(depth) {}
/**
* @return If the specified dimension is equal to this one
*/
constexpr inline bool operator==(const Dimensions &dimensions) {
return (width == dimensions.width) && (height == dimensions.height) && (depth == dimensions.depth);
}
/**
* @return If the specified dimension is not equal to this one
*/
constexpr inline bool operator!=(const Dimensions &dimensions) {
return (width != dimensions.width) || (height != dimensions.height) || (depth != dimensions.depth);
}
};
/**
* @brief This is used to hold the attributes of a texture format
*/
struct Format {
u8 bpb; //!< Bytes Per Block, this is to accommodate compressed formats
u16 blockHeight; //!< The height of a single block
u16 blockWidth; //!< The width of a single block
vk::Format vkFormat; //!< The underlying Vulkan type of the format
/**
* @return If this is a compressed texture format or not
*/
inline constexpr bool IsCompressed() {
return (blockHeight != 1) || (blockWidth != 1);
}
/**
* @param width The width of the texture in pixels
* @param height The height of the texture in pixels
* @param depth The depth of the texture in layers
* @return The size of the texture in bytes
*/
inline constexpr size_t GetSize(u32 width, u32 height, u32 depth = 1) {
return (((width / blockWidth) * (height / blockHeight)) * bpb) * depth;
}
/**
* @param dimensions The dimensions of a texture
* @return The size of the texture in bytes
*/
inline constexpr size_t GetSize(Dimensions dimensions) {
return GetSize(dimensions.width, dimensions.height, dimensions.depth);
}
/**
* @return If the specified format is equal to this one
*/
inline constexpr bool operator==(const Format &format) {
return vkFormat == format.vkFormat;
}
/**
* @return If the specified format is not equal to this one
*/
inline constexpr bool operator!=(const Format &format) {
return vkFormat != format.vkFormat;
}
/**
* @return If this format is actually valid or not
*/
inline constexpr operator bool() {
return bpb;
}
};
namespace format {
constexpr Format RGBA8888Unorm{sizeof(u8) * 4, 1, 1, vk::Format::eR8G8B8A8Unorm}; //!< 8-bits per channel 4-channel pixels
constexpr Format RGB565Unorm{sizeof(u8) * 2, 1, 1, vk::Format::eR5G6B5UnormPack16}; //!< Red channel: 5-bit, Green channel: 6-bit, Blue channel: 5-bit
}
/**
* @brief This describes the linearity of a texture. Refer to Chapter 20.1 of the Tegra X1 TRM for information.
*/
enum class TileMode {
Linear, //!< This is a purely linear texture
Pitch, //!< This is a pitch-linear texture
Block, //!< This is a 16Bx2 block-linear texture
};
/**
* @brief This holds the parameters of the tiling mode, covered in Table 76 in the Tegra X1 TRM
*/
union TileConfig {
struct {
u8 blockHeight; //!< The height of the blocks in GOBs
u8 blockDepth; //!< The depth of the blocks in GOBs
u16 surfaceWidth; //!< The width of a surface in samples
};
u32 pitch; //!< The pitch of the texture if it's pitch linear
};
/**
* @brief This enumerates all of the channel swizzle options
*/
enum class SwizzleChannel {
Zero, //!< Write 0 to the channel
One, //!< Write 1 to the channel
Red, //!< Red color channel
Green, //!< Green color channel
Blue, //!< Blue color channel
Alpha, //!< Alpha channel
};
/**
* @brief This holds all of the texture swizzles on each color channel
*/
struct Swizzle {
SwizzleChannel red{SwizzleChannel::Red}; //!< Swizzle for the red channel
SwizzleChannel green{SwizzleChannel::Green}; //!< Swizzle for the green channel
SwizzleChannel blue{SwizzleChannel::Blue}; //!< Swizzle for the blue channel
SwizzleChannel alpha{SwizzleChannel::Alpha}; //!< Swizzle for the alpha channel
};
}
class Texture;
class PresentationTexture;
/**
* @brief This class is used to hold metadata about a guest texture and can be used to create a host Texture object
*/
class GuestTexture : public std::enable_shared_from_this<GuestTexture> {
private:
const DeviceState &state; //!< The state of the device
public:
u64 address; //!< The address of the texture in guest memory
std::shared_ptr<Texture> host; //!< The corresponding host texture object
texture::Dimensions dimensions; //!< The dimensions of the texture
texture::Format format; //!< The format of the texture
texture::TileMode tileMode; //!< The tiling mode of the texture
texture::TileConfig tileConfig; //!< The tiling configuration of the texture
GuestTexture(const DeviceState &state, u64 address, texture::Dimensions dimensions, texture::Format format, texture::TileMode tileMode = texture::TileMode::Linear, texture::TileConfig tileConfig = {});
inline constexpr size_t Size() {
return format.GetSize(dimensions);
}
/**
* @brief This creates a corresponding host texture object for this guest texture
* @param format The format of the host texture (Defaults to the format of the guest texture)
* @param dimensions The dimensions of the host texture (Defaults to the dimensions of the host texture)
* @param swizzle The channel swizzle of the host texture (Defaults to no channel swizzling)
* @return A shared pointer to the host texture object
* @note There can only be one host texture for a corresponding guest texture
*/
std::shared_ptr<Texture> InitializeTexture(std::optional<texture::Format> format = std::nullopt, std::optional<texture::Dimensions> dimensions = std::nullopt, texture::Swizzle swizzle = {}) {
if (host)
throw exception("Trying to create multiple Texture objects from a single GuestTexture");
host = std::make_shared<Texture>(state, shared_from_this(), dimensions ? *dimensions : this->dimensions, format ? *format : this->format, swizzle);
return host;
}
protected:
std::shared_ptr<PresentationTexture> InitializePresentationTexture() {
if (host)
throw exception("Trying to create multiple PresentationTexture objects from a single GuestTexture");
auto presentation = std::make_shared<PresentationTexture>(state, shared_from_this(), dimensions, format);
host = std::static_pointer_cast<Texture>(presentation);
return presentation;
}
friend service::hosbinder::IHOSBinderDriver;
};
/**
* @brief This class is used to store a texture which is backed by host objects
*/
class Texture {
private:
const DeviceState &state; //!< The state of the device
public:
std::vector<u8> backing; //!< The object that holds a host copy of the guest texture (Will be replaced with a vk::Image)
std::shared_ptr<GuestTexture> guest; //!< The corresponding guest texture object
texture::Dimensions dimensions; //!< The dimensions of the texture
texture::Format format; //!< The format of the host texture
texture::Swizzle swizzle; //!< The swizzle of the host texture
public:
Texture(const DeviceState &state, std::shared_ptr<GuestTexture> guest, texture::Dimensions dimensions, texture::Format format, texture::Swizzle swizzle);
public:
/**
* @brief This convert this texture to the specified tiling mode
* @param tileMode The tiling mode to convert it to
* @param tileConfig The configuration for the tiling mode (Can be default argument for Linear)
*/
void ConvertTileMode(texture::TileMode tileMode, texture::TileConfig tileConfig = {});
/**
* @brief This sets the texture dimensions to the specified ones (As long as they are within the GuestTexture's range)
* @param dimensions The dimensions to adjust the texture to
*/
void SetDimensions(texture::Dimensions dimensions);
/**
* @brief This sets the format to the specified one
* @param format The format to change the texture to
*/
void SetFormat(texture::Format format);
/**
* @brief This sets the channel swizzle to the specified one
* @param swizzle The channel swizzle to the change the texture to
*/
void SetSwizzle(texture::Swizzle swizzle);
/**
* @brief This synchronizes the host texture with the guest after it has been modified
*/
void SynchronizeHost();
/**
* @brief This synchronizes the guest texture with the host texture after it has been modified
*/
void SynchronizeGuest();
};
/**
* @brief This class is used to hold a texture object alongside a release callback used for display presentation
*/
class PresentationTexture : public Texture {
public:
std::function<void()> releaseCallback; //!< The release callback after this texture has been displayed
PresentationTexture(const DeviceState &state, const std::shared_ptr<GuestTexture> &guest, const texture::Dimensions &dimensions, const texture::Format &format, const std::function<void()> &releaseCallback = {});
/**
* @return The corresponding Android surface format for the current texture format
*/
i32 GetAndroidFormat();
};
}
}