/** ****************************************************************************** * 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/xboxkrnl_video.h" #include "xenia/base/logging.h" #include "xenia/emulator.h" #include "xenia/gpu/graphics_system.h" #include "xenia/gpu/texture_info.h" #include "xenia/gpu/xenos.h" #include "xenia/kernel/kernel_state.h" #include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_private.h" #include "xenia/kernel/xboxkrnl/xboxkrnl_rtl.h" #include "xenia/xbox.h" // BT.709 on modern monitors and TVs looks the closest to the Xbox 360 connected // to an HDTV. DEFINE_uint32(kernel_display_gamma_type, 2, "Display gamma type: 0 - linear, 1 - sRGB (CRT), 2 - BT.709 " "(HDTV), 3 - power specified via kernel_display_gamma_power.", "Kernel"); UPDATE_from_uint32(kernel_display_gamma_type, 2020, 12, 31, 13, 1); DEFINE_double(kernel_display_gamma_power, 2.22222233, "Display gamma to use with kernel_display_gamma_type 3.", "Kernel"); namespace xe { namespace kernel { namespace xboxkrnl { // https://web.archive.org/web/20150805074003/https://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 // https://web.archive.org/web/20090428095215/https://msdn.microsoft.com/en-us/library/bb313877.aspx // https://web.archive.org/web/20100423054747/https://msdn.microsoft.com/en-us/library/bb313961.aspx // https://web.archive.org/web/20100423054747/https://msdn.microsoft.com/en-us/library/bb313878.aspx // https://web.archive.org/web/20090510235238/https://msdn.microsoft.com/en-us/library/bb313942.aspx // https://svn.dd-wrt.com/browser/src/linux/universal/linux-3.8/drivers/gpu/drm/radeon/radeon_ring.c?rev=21595 // https://www.microsoft.com/en-za/download/details.aspx?id=5313 -- "Stripped // Down Direct3D: Xbox 360 Command Buffer and Resource Management" void VdGetCurrentDisplayGamma(lpdword_t type_ptr, lpfloat_t power_ptr) { // 1 - sRGB. // 2 - TV (BT.709). // 3 - use the power written to *power_ptr. // Anything else - linear. // Used in D3D SetGammaRamp/SetPWLGamma to adjust the ramp for the display. *type_ptr = cvars::kernel_display_gamma_type; *power_ptr = float(cvars::kernel_display_gamma_power); } DECLARE_XBOXKRNL_EXPORT1(VdGetCurrentDisplayGamma, kVideo, kStub); struct X_D3DPRIVATE_RECT { xe::be x1; // 0x0 xe::be y1; // 0x4 xe::be x2; // 0x8 xe::be y2; // 0xC }; static_assert_size(X_D3DPRIVATE_RECT, 0x10); struct X_D3DFILTER_PARAMETERS { xe::be nyquist; // 0x0 xe::be flicker_filter; // 0x4 xe::be beta; // 0x8 }; static_assert_size(X_D3DFILTER_PARAMETERS, 0xC); struct X_D3DPRIVATE_SCALER_PARAMETERS { X_D3DPRIVATE_RECT scaler_source_rect; // 0x0 xe::be scaled_output_width; // 0x10 xe::be scaled_output_height; // 0x14 xe::be vertical_filter_type; // 0x18 X_D3DFILTER_PARAMETERS vertical_filter_parameters; // 0x1C xe::be horizontal_filter_type; // 0x28 X_D3DFILTER_PARAMETERS horizontal_filter_parameters; // 0x2C }; static_assert_size(X_D3DPRIVATE_SCALER_PARAMETERS, 0x38); struct X_DISPLAY_INFO { xe::be front_buffer_width; // 0x0 xe::be front_buffer_height; // 0x2 uint8_t front_buffer_color_format; // 0x4 uint8_t front_buffer_pixel_format; // 0x5 X_D3DPRIVATE_SCALER_PARAMETERS scaler_parameters; // 0x8 xe::be display_window_overscan_left; // 0x40 xe::be display_window_overscan_top; // 0x42 xe::be display_window_overscan_right; // 0x44 xe::be display_window_overscan_bottom; // 0x46 xe::be display_width; // 0x48 xe::be display_height; // 0x4A xe::be display_refresh_rate; // 0x4C xe::be display_interlaced; // 0x50 uint8_t display_color_format; // 0x54 xe::be actual_display_width; // 0x56 }; static_assert_size(X_DISPLAY_INFO, 0x58); void VdQueryVideoMode(pointer_t video_mode); void VdGetCurrentDisplayInformation(pointer_t display_info) { X_VIDEO_MODE mode; VdQueryVideoMode(&mode); display_info.Zero(); display_info->front_buffer_width = (uint16_t)mode.display_width; display_info->front_buffer_height = (uint16_t)mode.display_height; display_info->scaler_parameters.scaler_source_rect.x2 = mode.display_width; display_info->scaler_parameters.scaler_source_rect.y2 = mode.display_height; display_info->scaler_parameters.scaled_output_width = mode.display_width; display_info->scaler_parameters.scaled_output_height = mode.display_height; display_info->scaler_parameters.horizontal_filter_type = 1; display_info->scaler_parameters.vertical_filter_type = 1; display_info->display_window_overscan_left = 320; display_info->display_window_overscan_top = 180; display_info->display_window_overscan_right = 320; display_info->display_window_overscan_bottom = 180; display_info->display_width = (uint16_t)mode.display_width; display_info->display_height = (uint16_t)mode.display_height; display_info->display_refresh_rate = mode.refresh_rate; display_info->actual_display_width = (uint16_t)mode.display_width; } DECLARE_XBOXKRNL_EXPORT1(VdGetCurrentDisplayInformation, kVideo, kStub); void VdQueryVideoMode(pointer_t video_mode) { // TODO(benvanik): get info from actual display. video_mode.Zero(); 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 = 0x4A; video_mode->unknown_0x01 = 0x01; } DECLARE_XBOXKRNL_EXPORT1(VdQueryVideoMode, kVideo, kStub); dword_result_t VdQueryVideoFlags() { X_VIDEO_MODE mode; VdQueryVideoMode(&mode); uint32_t flags = 0; flags |= mode.is_widescreen ? 1 : 0; flags |= mode.display_width >= 1024 ? 2 : 0; flags |= mode.display_width >= 1920 ? 4 : 0; return flags; } DECLARE_XBOXKRNL_EXPORT1(VdQueryVideoFlags, kVideo, kStub); dword_result_t VdSetDisplayMode(dword_t flags) { // Often 0x40000000. // 0?ccf000 00000000 00000000 000000r0 // r: 0x00000002 | 1 // f: 0x08000000 | 27 // c: 0x30000000 | 28-29 // ?: 0x40000000 | 30 // r: 1 = Resolution is 720x480 or 720x576 // f: 1 = Texture format is k_2_10_10_10 or k_2_10_10_10_AS_16_16_16_16 // c: Color space (0 = RGB, 1 = ?, 2 = ?) // ?: (always set?) return 0; } DECLARE_XBOXKRNL_EXPORT1(VdSetDisplayMode, kVideo, kStub); dword_result_t VdSetDisplayModeOverride(unknown_t unk0, unknown_t unk1, double_t refresh_rate, unknown_t unk3, unknown_t unk4) { // refresh_rate = 0, 50, 59.9, etc. return 0; } DECLARE_XBOXKRNL_EXPORT1(VdSetDisplayModeOverride, kVideo, kStub); dword_result_t VdInitializeEngines(unknown_t unk0, function_t callback, lpvoid_t arg, lpdword_t pfp_ptr, lpdword_t me_ptr) { // r3 = 0x4F810000 // r4 = function ptr (cleanup callback?) // r5 = function arg // r6 = PFP Microcode // r7 = ME Microcode return 1; } DECLARE_XBOXKRNL_EXPORT1(VdInitializeEngines, kVideo, kStub); void VdShutdownEngines() { // Ignored for now. // Games seem to call an Initialize/Shutdown pair to query info, then // re-initialize. } DECLARE_XBOXKRNL_EXPORT1(VdShutdownEngines, kVideo, kStub); dword_result_t VdGetGraphicsAsicID() { // Games compare for < 0x10 and do VdInitializeEDRAM, else other // (retrain/etc). return 0x11; } DECLARE_XBOXKRNL_EXPORT1(VdGetGraphicsAsicID, kVideo, kStub); dword_result_t VdEnableDisableClockGating(dword_t enabled) { // Ignored, as it really doesn't matter. return 0; } DECLARE_XBOXKRNL_EXPORT1(VdEnableDisableClockGating, kVideo, kStub); void VdSetGraphicsInterruptCallback(function_t callback, lpvoid_t user_data) { // callback takes 2 params // r3 = bool 0/1 - 0 is normal interrupt, 1 is some acquire/lock mumble // r4 = user_data (r4 of VdSetGraphicsInterruptCallback) auto graphics_system = kernel_state()->emulator()->graphics_system(); graphics_system->SetInterruptCallback(callback, user_data); } DECLARE_XBOXKRNL_EXPORT1(VdSetGraphicsInterruptCallback, kVideo, kImplemented); void VdInitializeRingBuffer(lpvoid_t ptr, int_t size_log2) { // r3 = result of MmGetPhysicalAddress // r4 = log2(size) // Buffer pointers are from MmAllocatePhysicalMemory with WRITE_COMBINE. auto graphics_system = kernel_state()->emulator()->graphics_system(); graphics_system->InitializeRingBuffer(ptr, size_log2); } DECLARE_XBOXKRNL_EXPORT1(VdInitializeRingBuffer, kVideo, kImplemented); void VdEnableRingBufferRPtrWriteBack(lpvoid_t ptr, int_t block_size_log2) { // r4 = log2(block size), 6, usually --- <=19 auto graphics_system = kernel_state()->emulator()->graphics_system(); graphics_system->EnableReadPointerWriteBack(ptr, block_size_log2); } DECLARE_XBOXKRNL_EXPORT1(VdEnableRingBufferRPtrWriteBack, kVideo, kImplemented); void VdGetSystemCommandBuffer(lpunknown_t p0_ptr, lpunknown_t p1_ptr) { p0_ptr.Zero(0x94); xe::store_and_swap(p0_ptr, 0xBEEF0000); xe::store_and_swap(p1_ptr, 0xBEEF0001); } DECLARE_XBOXKRNL_EXPORT1(VdGetSystemCommandBuffer, kVideo, kStub); void VdSetSystemCommandBufferGpuIdentifierAddress(lpunknown_t unk) { // r3 = 0x2B10(d3d?) + 8 } DECLARE_XBOXKRNL_EXPORT1(VdSetSystemCommandBufferGpuIdentifierAddress, kVideo, kStub); // VdVerifyMEInitCommand // r3 // r4 = 19 // no op? dword_result_t VdInitializeScalerCommandBuffer( dword_t scaler_source_xy, // ((uint16_t)y << 16) | (uint16_t)x dword_t scaler_source_wh, // ((uint16_t)h << 16) | (uint16_t)w dword_t scaled_output_xy, // ((uint16_t)y << 16) | (uint16_t)x dword_t scaled_output_wh, // ((uint16_t)h << 16) | (uint16_t)w dword_t front_buffer_wh, // ((uint16_t)h << 16) | (uint16_t)w dword_t vertical_filter_type, // 7? pointer_t vertical_filter_params, // dword_t horizontal_filter_type, // 7? pointer_t horizontal_filter_params, // lpvoid_t unk9, // lpvoid_t dest_ptr, // Points to the first 80000000h where the memcpy // sources from. dword_t dest_count // Count in words. ) { // We could fake the commands here, but I'm not sure the game checks for // anything but success (non-zero ret). // For now, we just fill it with NOPs. auto dest = dest_ptr.as_array(); for (size_t i = 0; i < dest_count; ++i) { dest[i] = 0x80000000; } return (uint32_t)dest_count; } DECLARE_XBOXKRNL_EXPORT2(VdInitializeScalerCommandBuffer, kVideo, kImplemented, kSketchy); struct BufferScaling { xe::be fb_width; xe::be fb_height; xe::be bb_width; xe::be bb_height; }; void AppendParam(StringBuffer* string_buffer, pointer_t param) { string_buffer->AppendFormat( "{:08X}(scale {}x{} -> {}x{}))", param.guest_address(), uint16_t(param->bb_width), uint16_t(param->bb_height), uint16_t(param->fb_width), uint16_t(param->fb_height)); } dword_result_t VdCallGraphicsNotificationRoutines( unknown_t unk0, pointer_t args_ptr) { assert_true(unk0 == 1); // TODO(benvanik): what does this mean, I forget: // callbacks get 0, r3, r4 return 0; } DECLARE_XBOXKRNL_EXPORT2(VdCallGraphicsNotificationRoutines, kVideo, kImplemented, kSketchy); dword_result_t VdIsHSIOTrainingSucceeded() { // BOOL return value return 1; } DECLARE_XBOXKRNL_EXPORT1(VdIsHSIOTrainingSucceeded, kVideo, kStub); dword_result_t VdPersistDisplay(unknown_t unk0, lpdword_t unk1_ptr) { // unk1_ptr needs to be populated with a pointer passed to // MmFreePhysicalMemory(1, *unk1_ptr). if (unk1_ptr) { auto heap = kernel_memory()->LookupHeapByType(true, 16 * 1024); uint32_t unk1_value; heap->Alloc(64, 32, kMemoryAllocationReserve | kMemoryAllocationCommit, kMemoryProtectNoAccess, false, &unk1_value); *unk1_ptr = unk1_value; } return 1; } DECLARE_XBOXKRNL_EXPORT2(VdPersistDisplay, kVideo, kImplemented, kSketchy); dword_result_t VdRetrainEDRAMWorker(unknown_t unk0) { return 0; } DECLARE_XBOXKRNL_EXPORT1(VdRetrainEDRAMWorker, kVideo, kStub); dword_result_t VdRetrainEDRAM(unknown_t unk0, unknown_t unk1, unknown_t unk2, unknown_t unk3, unknown_t unk4, unknown_t unk5) { return 0; } DECLARE_XBOXKRNL_EXPORT1(VdRetrainEDRAM, kVideo, kStub); void VdSwap(lpvoid_t buffer_ptr, // ptr into primary ringbuffer lpvoid_t fetch_ptr, // frontbuffer Direct3D 9 texture header fetch lpunknown_t unk2, // system writeback ptr lpunknown_t unk3, // buffer from VdGetSystemCommandBuffer lpunknown_t unk4, // from VdGetSystemCommandBuffer (0xBEEF0001) lpdword_t frontbuffer_ptr, // ptr to frontbuffer address lpdword_t texture_format_ptr, lpdword_t color_space_ptr, lpdword_t width, lpdword_t height) { // All of these parameters are REQUIRED. assert(buffer_ptr); assert(fetch_ptr); assert(frontbuffer_ptr); assert(texture_format_ptr); assert(width); assert(height); namespace xenos = xe::gpu::xenos; xenos::xe_gpu_texture_fetch_t gpu_fetch; xe::copy_and_swap_32_unaligned( &gpu_fetch, reinterpret_cast(fetch_ptr.host_address()), 6); // The fetch constant passed is not a true GPU fetch constant, but rather, the // fetch constant stored in the Direct3D 9 texture header, which contains the // address in one of the virtual mappings of the physical memory rather than // the physical address itself. We're emulating swapping in the GPU subsystem, // which works with GPU memory addresses (physical addresses directly) from // proper fetch constants like ones used to bind textures to shaders, not CPU // MMU addresses, so translation from virtual to physical is needed. uint32_t frontbuffer_virtual_address = gpu_fetch.base_address << 12; assert_true(*frontbuffer_ptr == frontbuffer_virtual_address); uint32_t frontbuffer_physical_address = kernel_memory()->GetPhysicalAddress(frontbuffer_virtual_address); assert_true(frontbuffer_physical_address != UINT32_MAX); if (frontbuffer_physical_address == UINT32_MAX) { // Xenia-specific safety check. XELOGE("VdSwap: Invalid front buffer virtual address 0x{:08X}", frontbuffer_virtual_address); return; } gpu_fetch.base_address = frontbuffer_physical_address >> 12; auto texture_format = gpu::xenos::TextureFormat(texture_format_ptr.value()); auto color_space = *color_space_ptr; assert_true(texture_format == gpu::xenos::TextureFormat::k_8_8_8_8 || texture_format == gpu::xenos::TextureFormat::k_2_10_10_10_AS_16_16_16_16); assert_true(color_space == 0); // RGB(0) assert_true(*width == 1 + gpu_fetch.size_2d.width); assert_true(*height == 1 + gpu_fetch.size_2d.height); // 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. buffer_ptr.Zero(64 * 4); uint32_t offset = 0; auto dwords = buffer_ptr.as_array(); // Write in the GPU texture fetch. dwords[offset++] = xenos::MakePacketType0(gpu::XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0, 6); dwords[offset++] = gpu_fetch.dword_0; dwords[offset++] = gpu_fetch.dword_1; dwords[offset++] = gpu_fetch.dword_2; dwords[offset++] = gpu_fetch.dword_3; dwords[offset++] = gpu_fetch.dword_4; dwords[offset++] = gpu_fetch.dword_5; dwords[offset++] = xenos::MakePacketType3(xenos::PM4_XE_SWAP, 4); dwords[offset++] = xe::gpu::xenos::kSwapSignature; dwords[offset++] = frontbuffer_physical_address; dwords[offset++] = *width; dwords[offset++] = *height; // Fill the rest of the buffer with NOP packets. for (uint32_t i = offset; i < 64; i++) { dwords[i] = xenos::MakePacketType2(); } } DECLARE_XBOXKRNL_EXPORT2(VdSwap, kVideo, kImplemented, kImportant); void RegisterVideoExports(xe::cpu::ExportResolver* export_resolver, KernelState* kernel_state) { auto memory = kernel_state->memory(); // 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 = memory->SystemHeapAlloc(4, 32, kSystemHeapPhysical); export_resolver->SetVariableMapping("xboxkrnl.exe", ordinals::VdGlobalDevice, pVdGlobalDevice); xe::store_and_swap(memory->TranslateVirtual(pVdGlobalDevice), 0); // VdGlobalXamDevice (4b) // Pointer to the XAM D3D device, which we don't have. uint32_t pVdGlobalXamDevice = memory->SystemHeapAlloc(4, 32, kSystemHeapPhysical); export_resolver->SetVariableMapping( "xboxkrnl.exe", ordinals::VdGlobalXamDevice, pVdGlobalXamDevice); xe::store_and_swap(memory->TranslateVirtual(pVdGlobalXamDevice), 0); // VdGpuClockInMHz (4b) // GPU clock. Xenos is 500MHz. Hope nothing is relying on this timing... uint32_t pVdGpuClockInMHz = memory->SystemHeapAlloc(4, 32, kSystemHeapPhysical); export_resolver->SetVariableMapping("xboxkrnl.exe", ordinals::VdGpuClockInMHz, pVdGpuClockInMHz); xe::store_and_swap(memory->TranslateVirtual(pVdGpuClockInMHz), 500); // VdHSIOCalibrationLock (28b) // CriticalSection. uint32_t pVdHSIOCalibrationLock = memory->SystemHeapAlloc(28, 32, kSystemHeapPhysical); export_resolver->SetVariableMapping( "xboxkrnl.exe", ordinals::VdHSIOCalibrationLock, pVdHSIOCalibrationLock); auto hsio_lock = memory->TranslateVirtual(pVdHSIOCalibrationLock); xeRtlInitializeCriticalSectionAndSpinCount(hsio_lock, pVdHSIOCalibrationLock, 10000); } } // namespace xboxkrnl } // namespace kernel } // namespace xe