501 lines
15 KiB
C++
501 lines
15 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2013 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include <xenia/kernel/xboxkrnl_video.h>
|
|
|
|
#include <xenia/emulator.h>
|
|
#include <xenia/cpu/cpu.h>
|
|
#include <xenia/gpu/gpu.h>
|
|
#include <xenia/gpu/xenos/packets.h>
|
|
#include <xenia/kernel/kernel_state.h>
|
|
#include <xenia/kernel/xboxkrnl_private.h>
|
|
#include <xenia/kernel/xboxkrnl_rtl.h>
|
|
#include <xenia/kernel/util/shim_utils.h>
|
|
|
|
|
|
using namespace xe;
|
|
using namespace xe::gpu;
|
|
using namespace xe::kernel;
|
|
using namespace xe::kernel::xboxkrnl;
|
|
|
|
|
|
namespace xe {
|
|
namespace kernel {
|
|
|
|
|
|
// http://www.tweakoz.com/orkid/
|
|
// http://www.tweakoz.com/orkid/dox/d3/d52/xb360init_8cpp_source.html
|
|
// https://github.com/Free60Project/xenosfb/
|
|
// https://github.com/Free60Project/xenosfb/blob/master/src/xe.h
|
|
// https://github.com/gligli/libxemit
|
|
// http://web.archive.org/web/20090428095215/http://msdn.microsoft.com/en-us/library/bb313877.aspx
|
|
// http://web.archive.org/web/20100423054747/http://msdn.microsoft.com/en-us/library/bb313961.aspx
|
|
// http://web.archive.org/web/20100423054747/http://msdn.microsoft.com/en-us/library/bb313878.aspx
|
|
// http://web.archive.org/web/20090510235238/http://msdn.microsoft.com/en-us/library/bb313942.aspx
|
|
// http://svn.dd-wrt.com/browser/src/linux/universal/linux-3.8/drivers/gpu/drm/radeon/radeon_ring.c
|
|
// http://www.microsoft.com/en-za/download/details.aspx?id=5313 -- "Stripped Down Direct3D: Xbox 360 Command Buffer and Resource Management"
|
|
|
|
|
|
void xeVdGetCurrentDisplayGamma(uint32_t* arg0, float* arg1) {
|
|
*arg0 = 2;
|
|
*arg1 = 2.22222233f;
|
|
}
|
|
|
|
|
|
SHIM_CALL VdGetCurrentDisplayGamma_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t arg0_ptr = SHIM_GET_ARG_32(0);
|
|
uint32_t arg1_ptr = SHIM_GET_ARG_32(1);
|
|
|
|
XELOGD(
|
|
"VdGetCurrentDisplayGamma(%.8X, %.8X)",
|
|
arg0_ptr, arg1_ptr);
|
|
|
|
uint32_t arg0 = 0;
|
|
union {
|
|
float float_value;
|
|
uint32_t uint_value;
|
|
} arg1;
|
|
xeVdGetCurrentDisplayGamma(&arg0, &arg1.float_value);
|
|
SHIM_SET_MEM_32(arg0_ptr, arg0);
|
|
SHIM_SET_MEM_32(arg1_ptr, arg1.uint_value);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdGetCurrentDisplayInformation_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t ptr = SHIM_GET_ARG_32(0);
|
|
|
|
XELOGD(
|
|
"VdGetCurrentDisplayInformation(%.8X)",
|
|
ptr);
|
|
|
|
// Expecting a length 0x58 struct of stuff.
|
|
SHIM_SET_MEM_32(ptr + 0x10, 1280);
|
|
SHIM_SET_MEM_32(ptr + 0x14, 720);
|
|
SHIM_SET_MEM_16(ptr + 0x48, 1280);
|
|
SHIM_SET_MEM_16(ptr + 0x4A, 720);
|
|
SHIM_SET_MEM_16(ptr + 0x56, 1280);
|
|
}
|
|
|
|
|
|
uint32_t xeVdQueryVideoFlags() {
|
|
// ?
|
|
return 0x00000007;
|
|
}
|
|
|
|
|
|
SHIM_CALL VdQueryVideoFlags_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
XELOGD(
|
|
"VdQueryVideoFlags()");
|
|
|
|
SHIM_SET_RETURN_64(xeVdQueryVideoFlags());
|
|
}
|
|
|
|
|
|
void xeVdQueryVideoMode(X_VIDEO_MODE *video_mode, bool swap) {
|
|
if (video_mode == NULL) {
|
|
return;
|
|
}
|
|
|
|
// TODO: get info from actual display
|
|
video_mode->display_width = 1280;
|
|
video_mode->display_height = 720;
|
|
video_mode->is_interlaced = 0;
|
|
video_mode->is_widescreen = 1;
|
|
video_mode->is_hi_def = 1;
|
|
video_mode->refresh_rate = 60.0f;
|
|
video_mode->video_standard = 1; // NTSC
|
|
video_mode->unknown_0x8a = 0x8A;
|
|
video_mode->unknown_0x01 = 0x01;
|
|
|
|
if (swap) {
|
|
video_mode->display_width = poly::byte_swap(video_mode->display_width);
|
|
video_mode->display_height = poly::byte_swap(video_mode->display_height);
|
|
video_mode->is_interlaced = poly::byte_swap(video_mode->is_interlaced);
|
|
video_mode->is_widescreen = poly::byte_swap(video_mode->is_widescreen);
|
|
video_mode->is_hi_def = poly::byte_swap(video_mode->is_hi_def);
|
|
video_mode->refresh_rate = poly::byte_swap(video_mode->refresh_rate);
|
|
video_mode->video_standard = poly::byte_swap(video_mode->video_standard);
|
|
video_mode->unknown_0x8a = poly::byte_swap(video_mode->unknown_0x8a);
|
|
video_mode->unknown_0x01 = poly::byte_swap(video_mode->unknown_0x01);
|
|
}
|
|
}
|
|
|
|
|
|
SHIM_CALL VdQueryVideoMode_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t video_mode_ptr = SHIM_GET_ARG_32(0);
|
|
X_VIDEO_MODE *video_mode = (X_VIDEO_MODE*)SHIM_MEM_ADDR(video_mode_ptr);
|
|
|
|
XELOGD(
|
|
"VdQueryVideoMode(%.8X)",
|
|
video_mode_ptr);
|
|
|
|
xeVdQueryVideoMode(video_mode, true);
|
|
}
|
|
|
|
|
|
void xeVdInitializeEngines(uint32_t unk0, uint32_t callback, uint32_t unk1,
|
|
uint32_t unk2_ptr, uint32_t unk3_ptr) {
|
|
KernelState* state = shared_kernel_state_;
|
|
assert_not_null(state);
|
|
GraphicsSystem* gs = state->emulator()->graphics_system();
|
|
if (!gs) {
|
|
return;
|
|
}
|
|
|
|
// r3 = 0x4F810000
|
|
// r4 = function ptr (cleanup callback?)
|
|
// r5 = 0
|
|
// r6/r7 = some binary data in .data
|
|
}
|
|
|
|
|
|
SHIM_CALL VdInitializeEngines_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t unk0 = SHIM_GET_ARG_32(0);
|
|
uint32_t callback = SHIM_GET_ARG_32(1);
|
|
uint32_t unk1 = SHIM_GET_ARG_32(2);
|
|
uint32_t unk2_ptr = SHIM_GET_ARG_32(3);
|
|
uint32_t unk3_ptr = SHIM_GET_ARG_32(4);
|
|
|
|
XELOGD(
|
|
"VdInitializeEngines(%.8X, %.8X, %.8X, %.8X, %.8X)",
|
|
unk0, callback, unk1, unk2_ptr, unk3_ptr);
|
|
|
|
xeVdInitializeEngines(unk0, callback, unk1, unk2_ptr, unk3_ptr);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdShutdownEngines_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
XELOGD(
|
|
"VdShutdownEngines()");
|
|
|
|
// Ignored for now.
|
|
// Games seem to call an Initialize/Shutdown pair to query info, then
|
|
// re-initialize.
|
|
}
|
|
|
|
|
|
void xeVdSetGraphicsInterruptCallback(uint32_t callback, uint32_t user_data) {
|
|
KernelState* state = shared_kernel_state_;
|
|
assert_not_null(state);
|
|
GraphicsSystem* gs = state->emulator()->graphics_system();
|
|
if (!gs) {
|
|
return;
|
|
}
|
|
|
|
// callback takes 2 params
|
|
// r3 = bool 0/1 - 0 is normal interrupt, 1 is some acquire/lock mumble
|
|
// r4 = user_data (r4 of VdSetGraphicsInterruptCallback)
|
|
|
|
gs->SetInterruptCallback(callback, user_data);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdSetGraphicsInterruptCallback_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t callback = SHIM_GET_ARG_32(0);
|
|
uint32_t user_data = SHIM_GET_ARG_32(1);
|
|
|
|
XELOGD(
|
|
"VdSetGraphicsInterruptCallback(%.8X, %.8X)",
|
|
callback, user_data);
|
|
|
|
xeVdSetGraphicsInterruptCallback(callback, user_data);
|
|
}
|
|
|
|
|
|
void xeVdInitializeRingBuffer(uint32_t ptr, uint32_t page_count) {
|
|
KernelState* state = shared_kernel_state_;
|
|
assert_not_null(state);
|
|
GraphicsSystem* gs = state->emulator()->graphics_system();
|
|
if (!gs) {
|
|
return;
|
|
}
|
|
|
|
// r3 = result of MmGetPhysicalAddress
|
|
// r4 = number of pages? page size?
|
|
// 0x8000 -> cntlzw=16 -> 0x1C - 16 = 12
|
|
// Buffer pointers are from MmAllocatePhysicalMemory with WRITE_COMBINE.
|
|
// Sizes could be zero? XBLA games seem to do this. Default sizes?
|
|
// D3D does size / region_count - must be > 1024
|
|
|
|
gs->InitializeRingBuffer(ptr, page_count);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdInitializeRingBuffer_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t ptr = SHIM_GET_ARG_32(0);
|
|
uint32_t page_count = SHIM_GET_ARG_32(1);
|
|
|
|
XELOGD(
|
|
"VdInitializeRingBuffer(%.8X, %.8X)",
|
|
ptr, page_count);
|
|
|
|
xeVdInitializeRingBuffer(ptr, page_count);
|
|
}
|
|
|
|
|
|
void xeVdEnableRingBufferRPtrWriteBack(uint32_t ptr, uint32_t block_size) {
|
|
KernelState* state = shared_kernel_state_;
|
|
assert_not_null(state);
|
|
GraphicsSystem* gs = state->emulator()->graphics_system();
|
|
if (!gs) {
|
|
return;
|
|
}
|
|
|
|
// r4 = 6, usually --- <=19
|
|
gs->EnableReadPointerWriteBack(ptr, block_size);
|
|
|
|
ptr += 0x20000000;
|
|
//printf("%.8X", ptr);
|
|
// 0x0110343c
|
|
|
|
// r3 = 0x2B10(d3d?) + 0x3C
|
|
|
|
//((p + 0x3C) & 0x1FFFFFFF) + ((((p + 0x3C) >> 20) + 0x200) & 0x1000)
|
|
//also 0x3C offset into WriteBacks is PrimaryRingBufferReadIndex
|
|
//(1:17:38 AM) Rick: .text:8201B348 lwz r11, 0x2B10(r31)
|
|
//(1:17:38 AM) Rick: .text:8201B34C addi r11, r11, 0x3C
|
|
//(1:17:38 AM) Rick: .text:8201B350 srwi r10, r11, 20 # r10 = r11 >> 20
|
|
//(1:17:38 AM) Rick: .text:8201B354 clrlwi r11, r11, 3 # r11 = r11 & 0x1FFFFFFF
|
|
//(1:17:38 AM) Rick: .text:8201B358 addi r10, r10, 0x200
|
|
//(1:17:39 AM) Rick: .text:8201B35C rlwinm r10, r10, 0,19,19 # r10 = r10 & 0x1000
|
|
//(1:17:39 AM) Rick: .text:8201B360 add r3, r10, r11
|
|
//(1:17:39 AM) Rick: .text:8201B364 bl VdEnableRingBufferRPtrWriteBack
|
|
// TODO(benvanik): something?
|
|
}
|
|
|
|
|
|
SHIM_CALL VdEnableRingBufferRPtrWriteBack_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t ptr = SHIM_GET_ARG_32(0);
|
|
uint32_t block_size = SHIM_GET_ARG_32(1);
|
|
|
|
XELOGD(
|
|
"VdEnableRingBufferRPtrWriteBack(%.8X, %.8X)",
|
|
ptr, block_size);
|
|
|
|
xeVdEnableRingBufferRPtrWriteBack(ptr, block_size);
|
|
}
|
|
|
|
|
|
void xeVdGetSystemCommandBuffer(uint32_t* p0, uint32_t* p1) {
|
|
*p0 = 0xBEEF0000;
|
|
*p1 = 0xBEEF0001;
|
|
}
|
|
|
|
|
|
SHIM_CALL VdGetSystemCommandBuffer_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t p0_ptr = SHIM_GET_ARG_32(0);
|
|
uint32_t p1_ptr = SHIM_GET_ARG_32(1);
|
|
|
|
XELOGD(
|
|
"VdGetSystemCommandBuffer(%.8X, %.8X)",
|
|
p0_ptr,
|
|
p1_ptr);
|
|
|
|
uint32_t p0 = 0;
|
|
uint32_t p1 = 0;
|
|
xeVdGetSystemCommandBuffer(&p0, &p1);
|
|
SHIM_SET_MEM_32(p0_ptr, p0);
|
|
SHIM_SET_MEM_32(p1_ptr, p1);
|
|
}
|
|
|
|
|
|
void xeVdSetSystemCommandBufferGpuIdentifierAddress(uint32_t unk) {
|
|
KernelState* state = shared_kernel_state_;
|
|
assert_not_null(state);
|
|
GraphicsSystem* gs = state->emulator()->graphics_system();
|
|
if (!gs) {
|
|
return;
|
|
}
|
|
|
|
// r3 = 0x2B10(d3d?) + 8
|
|
}
|
|
|
|
|
|
SHIM_CALL VdSetSystemCommandBufferGpuIdentifierAddress_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t unk = SHIM_GET_ARG_32(0);
|
|
|
|
XELOGD(
|
|
"VdSetSystemCommandBufferGpuIdentifierAddress(%.8X)",
|
|
unk);
|
|
|
|
xeVdSetSystemCommandBufferGpuIdentifierAddress(unk);
|
|
}
|
|
|
|
|
|
// VdVerifyMEInitCommand
|
|
// r3
|
|
// r4 = 19
|
|
// no op?
|
|
|
|
|
|
// VdCallGraphicsNotificationRoutines
|
|
// r3 = 1
|
|
// r4 = ?
|
|
// callbacks get 0, r3, r4
|
|
|
|
|
|
SHIM_CALL VdIsHSIOTrainingSucceeded_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
XELOGD(
|
|
"VdIsHSIOTrainingSucceeded()");
|
|
|
|
// Not really sure what this should be - code does weird stuff here:
|
|
// (cntlzw r11, r3 / extrwi r11, r11, 1, 26)
|
|
SHIM_SET_RETURN_64(1);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdPersistDisplay_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
XELOGD(
|
|
"VdPersistDisplay(?)");
|
|
|
|
// ?
|
|
SHIM_SET_RETURN_64(1);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdRetrainEDRAMWorker_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t unk0 = SHIM_GET_ARG_32(0);
|
|
|
|
XELOGD(
|
|
"VdRetrainEDRAMWorker(%.8X)",
|
|
unk0);
|
|
|
|
SHIM_SET_RETURN_64(0);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdRetrainEDRAM_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t unk0 = SHIM_GET_ARG_32(0);
|
|
uint32_t unk1 = SHIM_GET_ARG_32(1);
|
|
uint32_t unk2 = SHIM_GET_ARG_32(2);
|
|
uint32_t unk3 = SHIM_GET_ARG_32(3);
|
|
uint32_t unk4 = SHIM_GET_ARG_32(4);
|
|
uint32_t unk5 = SHIM_GET_ARG_32(5);
|
|
|
|
XELOGD(
|
|
"VdRetrainEDRAM(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X)",
|
|
unk0, unk1, unk2, unk3, unk4, unk5);
|
|
|
|
SHIM_SET_RETURN_64(0);
|
|
}
|
|
|
|
|
|
SHIM_CALL VdSwap_shim(
|
|
PPCContext* ppc_state, KernelState* state) {
|
|
uint32_t unk0 = SHIM_GET_ARG_32(0); // ptr into primary ringbuffer
|
|
uint32_t unk1 = SHIM_GET_ARG_32(1);
|
|
uint32_t unk2 = SHIM_GET_ARG_32(2);
|
|
uint32_t unk3 = SHIM_GET_ARG_32(3); // ptr to 0xBEEF0000
|
|
uint32_t unk4 = SHIM_GET_ARG_32(4); // 0xBEEF0001
|
|
uint32_t unk5 = SHIM_GET_ARG_32(5);
|
|
uint32_t unk6 = SHIM_GET_ARG_32(6);
|
|
uint32_t unk7 = SHIM_GET_ARG_32(7);
|
|
|
|
XELOGD(
|
|
"VdSwap(%.8X, %.8X, %.8X, %.8X, %.8X, %.8X, %.8X, %.8X)",
|
|
unk0,
|
|
unk1,
|
|
unk2,
|
|
unk3,
|
|
unk4,
|
|
unk5,
|
|
unk6,
|
|
unk7);
|
|
|
|
// The caller seems to reserve 64 words (256b) in the primary ringbuffer
|
|
// for this method to do what it needs. We just zero them out and send a
|
|
// token value. It'd be nice to figure out what this is really doing so
|
|
// that we could simulate it, though due to TCR I bet all games need to
|
|
// use this method.
|
|
xe_zero_struct(SHIM_MEM_ADDR(unk0), 64 * 4);
|
|
auto dwords = reinterpret_cast<uint32_t*>(SHIM_MEM_ADDR(unk0));
|
|
dwords[0] = poly::byte_swap((0x03 << 30) |
|
|
((1 - 1) << 16) |
|
|
(xenos::PM4_XE_SWAP << 8));
|
|
|
|
SHIM_SET_RETURN_64(0);
|
|
}
|
|
|
|
|
|
} // namespace kernel
|
|
} // namespace xe
|
|
|
|
|
|
void xe::kernel::xboxkrnl::RegisterVideoExports(
|
|
ExportResolver* export_resolver, KernelState* state) {
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdGetCurrentDisplayGamma, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdGetCurrentDisplayInformation, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdQueryVideoFlags, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdQueryVideoMode, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdInitializeEngines, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdShutdownEngines, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdSetGraphicsInterruptCallback, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdInitializeRingBuffer, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdEnableRingBufferRPtrWriteBack, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdGetSystemCommandBuffer, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe",
|
|
VdSetSystemCommandBufferGpuIdentifierAddress, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdIsHSIOTrainingSucceeded, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdPersistDisplay, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdRetrainEDRAMWorker, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdRetrainEDRAM, state);
|
|
SHIM_SET_MAPPING("xboxkrnl.exe", VdSwap, state);
|
|
|
|
Memory* memory = state->memory();
|
|
uint8_t* mem = memory->membase();
|
|
|
|
// VdGlobalDevice (4b)
|
|
// Pointer to a global D3D device. Games only seem to set this, so we don't
|
|
// have to do anything. We may want to read it back later, though.
|
|
uint32_t pVdGlobalDevice = (uint32_t)memory->HeapAlloc(0, 4, 0);
|
|
export_resolver->SetVariableMapping(
|
|
"xboxkrnl.exe", ordinals::VdGlobalDevice,
|
|
pVdGlobalDevice);
|
|
poly::store_and_swap<uint32_t>(mem + pVdGlobalDevice, 0);
|
|
|
|
// VdGlobalXamDevice (4b)
|
|
// Pointer to the XAM D3D device, which we don't have.
|
|
uint32_t pVdGlobalXamDevice = (uint32_t)memory->HeapAlloc(0, 4, 0);
|
|
export_resolver->SetVariableMapping(
|
|
"xboxkrnl.exe", ordinals::VdGlobalXamDevice,
|
|
pVdGlobalXamDevice);
|
|
poly::store_and_swap<uint32_t>(mem + pVdGlobalXamDevice, 0);
|
|
|
|
// VdGpuClockInMHz (4b)
|
|
// GPU clock. Xenos is 500MHz. Hope nothing is relying on this timing...
|
|
uint32_t pVdGpuClockInMHz = (uint32_t)memory->HeapAlloc(0, 4, 0);
|
|
export_resolver->SetVariableMapping(
|
|
"xboxkrnl.exe", ordinals::VdGpuClockInMHz,
|
|
pVdGpuClockInMHz);
|
|
poly::store_and_swap<uint32_t>(mem + pVdGpuClockInMHz, 500);
|
|
|
|
// VdHSIOCalibrationLock (28b)
|
|
// CriticalSection.
|
|
uint32_t pVdHSIOCalibrationLock = (uint32_t)memory->HeapAlloc(0, 28, 0);
|
|
export_resolver->SetVariableMapping(
|
|
"xboxkrnl.exe", ordinals::VdHSIOCalibrationLock,
|
|
pVdHSIOCalibrationLock);
|
|
xeRtlInitializeCriticalSectionAndSpinCount(pVdHSIOCalibrationLock, 10000);
|
|
}
|