text: /**
   ******************************************************************************
   * 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"
  
  namespace xe {
  namespace kernel {
  namespace xboxkrnl {
  
  // 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 VdGetCurrentDisplayGamma(lpdword_t arg0_ptr, lpfloat_t arg1_ptr) {
    *arg0_ptr = 2;
    *arg1_ptr = 2.22222233f;
  }
  DECLARE_XBOXKRNL_EXPORT(VdGetCurrentDisplayGamma, ExportTag::kVideo);
  
  struct X_D3DPRIVATE_RECT {
    xe::be<uint32_t> x1;  // 0x0
    xe::be<uint32_t> y1;  // 0x4
    xe::be<uint32_t> x2;  // 0x8
    xe::be<uint32_t> y2;  // 0xC
  };
  static_assert_size(X_D3DPRIVATE_RECT, 0x10);
  
  struct X_D3DFILTER_PARAMETERS {
    xe::be<float> nyquist;         // 0x0
    xe::be<float> flicker_filter;  // 0x4
    xe::be<float> beta;            // 0x8
  };
  static_assert_size(X_D3DFILTER_PARAMETERS, 0xC);
  
  struct X_D3DPRIVATE_SCALER_PARAMETERS {
    X_D3DPRIVATE_RECT scaler_source_rect;                 // 0x0
    xe::be<uint32_t> scaled_output_width;                 // 0x10
    xe::be<uint32_t> scaled_output_height;                // 0x14
    xe::be<uint32_t> vertical_filter_type;                // 0x18
    X_D3DFILTER_PARAMETERS vertical_filter_parameters;    // 0x1C
    xe::be<uint32_t> 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<uint16_t> front_buffer_width;               // 0x0
    xe::be<uint16_t> front_buffer_height;              // 0x2
    xe::be<uint8_t> front_buffer_color_format;         // 0x4
    xe::be<uint8_t> front_buffer_pixel_format;         // 0x5
    X_D3DPRIVATE_SCALER_PARAMETERS scaler_parameters;  // 0x8
    xe::be<uint16_t> display_window_overscan_left;     // 0x40
    xe::be<uint16_t> display_window_overscan_top;      // 0x42
    xe::be<uint16_t> display_window_overscan_right;    // 0x44
    xe::be<uint16_t> display_window_overscan_bottom;   // 0x46
    xe::be<uint16_t> display_width;                    // 0x48
    xe::be<uint16_t> display_height;                   // 0x4A
    xe::be<float> display_refresh_rate;                // 0x4C
    xe::be<uint32_t> display_interlaced;               // 0x50
    xe::be<uint8_t> display_color_format;              // 0x54
    xe::be<uint16_t> actual_display_width;             // 0x56
  };
  static_assert_size(X_DISPLAY_INFO, 0x58);
  
  void VdQueryVideoMode(pointer_t<X_VIDEO_MODE> video_mode);
  
  void VdGetCurrentDisplayInformation(pointer_t<X_DISPLAY_INFO> 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_EXPORT(VdGetCurrentDisplayInformation, ExportTag::kVideo);
  
  void VdQueryVideoMode(pointer_t<X_VIDEO_MODE> 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_EXPORT(VdQueryVideoMode, ExportTag::kVideo);
  
  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_EXPORT(VdQueryVideoFlags, ExportTag::kVideo);
  
  dword_result_t VdSetDisplayMode(dword_t mode) {
    // Often 0x40000000.
    return 0;
  }
  DECLARE_XBOXKRNL_EXPORT(VdSetDisplayMode, ExportTag::kVideo | ExportTag::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_EXPORT(VdSetDisplayModeOverride,
                          ExportTag::kVideo | ExportTag::kStub);
  
  dword_result_t VdInitializeEngines(unknown_t unk0, function_t callback,
                                     lpvoid_t arg, lpunknown_t unk2_ptr,
                                     lpunknown_t unk3_ptr) {
    // r3 = 0x4F810000
    // r4 = function ptr (cleanup callback?)
    // r5 = function arg
    // r6 = register init cmds(?)
    // r7 = gpu init cmds(?)
    return 1;
  }
  DECLARE_XBOXKRNL_EXPORT(VdInitializeEngines,
                          ExportTag::kVideo | ExportTag::kStub);
  
  void VdShutdownEngines() {
    // Ignored for now.
    // Games seem to call an Initialize/Shutdown pair to query info, then
    // re-initialize.
  }
  DECLARE_XBOXKRNL_EXPORT(VdShutdownEngines,
                          ExportTag::kVideo | ExportTag::kStub);
  
  dword_result_t VdGetGraphicsAsicID() {
    // Games compare for < 0x10 and do VdInitializeEDRAM, else other
    // (retrain/etc).
    return 0x11;
  }
  DECLARE_XBOXKRNL_EXPORT(VdGetGraphicsAsicID, ExportTag::kVideo);
  
  dword_result_t VdEnableDisableClockGating(dword_t enabled) {
    // Ignored, as it really doesn't matter.
    return 0;
  }
  DECLARE_XBOXKRNL_EXPORT(VdEnableDisableClockGating, ExportTag::kVideo);
  
  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_EXPORT(VdSetGraphicsInterruptCallback, ExportTag::kVideo);
  
  void VdInitializeRingBuffer(lpvoid_t ptr, int_t log2_size) {
    // r3 = result of MmGetPhysicalAddress
    // r4 = log2(size)
    // r4 is or'd with 0x802 and then stuffed into CP_RB_CNTL
    // according to AMD docs, this corresponds with RB_BUFSZ, which is log2
    // actual size.
    // 0x8 is RB_BLKSZ, or number of words gpu will read before updating the
    // host read pointer.
    // So being or'd with 0x2 makes the ring buffer size always a multiple of 4.
    // Buffer pointers are from MmAllocatePhysicalMemory with WRITE_COMBINE.
    auto graphics_system = kernel_state()->emulator()->graphics_system();
    graphics_system->InitializeRingBuffer(ptr, log2_size);
  }
  DECLARE_XBOXKRNL_EXPORT(VdInitializeRingBuffer, ExportTag::kVideo);
  
  void VdEnableRingBufferRPtrWriteBack(lpvoid_t ptr, int_t block_size) {
    // r4 = 6, usually --- <=19
    auto graphics_system = kernel_state()->emulator()->graphics_system();
    graphics_system->EnableReadPointerWriteBack(ptr, block_size);
  }
  DECLARE_XBOXKRNL_EXPORT(VdEnableRingBufferRPtrWriteBack, ExportTag::kVideo);
  
  void VdGetSystemCommandBuffer(lpunknown_t p0_ptr, lpunknown_t p1_ptr) {
    p0_ptr.Zero(0x94);
    xe::store_and_swap<uint32_t>(p0_ptr, 0xBEEF0000);
    xe::store_and_swap<uint32_t>(p1_ptr, 0xBEEF0001);
  }
  DECLARE_XBOXKRNL_EXPORT(VdGetSystemCommandBuffer,
                          ExportTag::kVideo | ExportTag::kStub);
  
  void VdSetSystemCommandBufferGpuIdentifierAddress(lpunknown_t unk) {
    // r3 = 0x2B10(d3d?) + 8
  }
  DECLARE_XBOXKRNL_EXPORT(VdSetSystemCommandBufferGpuIdentifierAddress,
                          ExportTag::kVideo | ExportTag::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<X_D3DFILTER_PARAMETERS> vertical_filter_params,    //
      dword_t horizontal_filter_type,                              // 7?
      pointer_t<X_D3DFILTER_PARAMETERS> 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<uint32_t>();
    for (size_t i = 0; i < dest_count; ++i) {
      dest[i] = 0x80000000;
    }
    return (uint32_t)dest_count;
  }
  DECLARE_XBOXKRNL_EXPORT(VdInitializeScalerCommandBuffer,
                          ExportTag::kVideo | ExportTag::kSketchy);
  
  struct BufferScaling {
    xe::be<uint16_t> fb_width;
    xe::be<uint16_t> fb_height;
    xe::be<uint16_t> bb_width;
    xe::be<uint16_t> bb_height;
  };
  void AppendParam(StringBuffer* string_buffer, pointer_t<BufferScaling> param) {
    string_buffer->AppendFormat(
        "%.8X(scale %dx%d -> %dx%d))", 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<BufferScaling> args_ptr) {
    assert_true(unk0 == 1);
  
    // TODO(benvanik): what does this mean, I forget:
    // callbacks get 0, r3, r4
    return 0;
  }
  DECLARE_XBOXKRNL_EXPORT(VdCallGraphicsNotificationRoutines,
                          ExportTag::kVideo | ExportTag::kSketchy);
  
  dword_result_t VdIsHSIOTrainingSucceeded() {
    // BOOL return value
    return 1;
  }
  DECLARE_XBOXKRNL_EXPORT(VdIsHSIOTrainingSucceeded,
                          ExportTag::kVideo | ExportTag::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_EXPORT(VdPersistDisplay,
                          ExportTag::kVideo | ExportTag::kSketchy);
  
  dword_result_t VdRetrainEDRAMWorker(unknown_t unk0) { return 0; }
  DECLARE_XBOXKRNL_EXPORT(VdRetrainEDRAMWorker,
                          ExportTag::kVideo | ExportTag::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_EXPORT(VdRetrainEDRAM, ExportTag::kVideo | ExportTag::kStub);
  
  void VdSwap(lpvoid_t buffer_ptr,  // ptr into primary ringbuffer
              lpvoid_t fetch_ptr,   // frontbuffer texture 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 fetch;
    xe::copy_and_swap_32_unaligned(
        &fetch, reinterpret_cast<uint32_t*>(fetch_ptr.host_address()), 6);
  
    auto texture_format = gpu::TextureFormat(texture_format_ptr.value());
    auto color_space = *color_space_ptr;
    assert_true(texture_format == gpu::TextureFormat::k_8_8_8_8 ||
                texture_format ==
                    gpu::TextureFormat::k_2_10_10_10_AS_16_16_16_16);
    assert_true(color_space == 0);  // RGB(0)
    assert_true(*frontbuffer_ptr == fetch.address << 12);
    assert_true(*width == 1 + fetch.size_2d.width);
    assert_true(*height == 1 + 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);
  
    // virtual -> physical
    fetch.address &= 0x1FFFF;
  
    uint32_t offset = 0;
    auto dwords = buffer_ptr.as_array<uint32_t>();
  
    // Write in the texture fetch.
    dwords[offset++] =
        xenos::MakePacketType0(gpu::XE_GPU_REG_SHADER_CONSTANT_FETCH_00_0, 6);
    dwords[offset++] = fetch.dword_0;
    dwords[offset++] = fetch.dword_1;
    dwords[offset++] = fetch.dword_2;
    dwords[offset++] = fetch.dword_3;
    dwords[offset++] = fetch.dword_4;
    dwords[offset++] = fetch.dword_5;
  
    dwords[offset++] = xenos::MakePacketType3(xenos::PM4_XE_SWAP, 4);
    dwords[offset++] = 'SWAP';
    dwords[offset++] = (*frontbuffer_ptr) & 0x1FFFFFFF;
  
    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_EXPORT(VdSwap, ExportTag::kVideo | ExportTag::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<uint32_t>(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<uint32_t>(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<uint32_t>(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<X_RTL_CRITICAL_SECTION*>(pVdHSIOCalibrationLock);
    xeRtlInitializeCriticalSectionAndSpinCount(hsio_lock, pVdHSIOCalibrationLock,
                                               10000);
  }
  
  }  // namespace xboxkrnl
  }  // namespace kernel
  }  // namespace xe
  