text: /******************************************************************************
   * The MIT License (MIT)
   *
   * Copyright (c) 2019-2021 Baldur Karlsson
   *
   * Permission is hereby granted, free of charge, to any person obtaining a copy
   * of this software and associated documentation files (the "Software"), to deal
   * in the Software without restriction, including without limitation the rights
   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   * copies of the Software, and to permit persons to whom the Software is
   * furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included in
   * all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
   * THE SOFTWARE.
   ******************************************************************************/
  
  #include "d3d11_context.h"
  #include "d3d11_debug.h"
  #include "d3d11_device.h"
  #include "d3d11_replay.h"
  
  void D3D11Replay::OutputWindow::MakeRTV()
  {
    ID3D11Texture2D *texture = NULL;
  
    HRESULT hr = S_OK;
  
    if(swap)
    {
      hr = swap->GetBuffer(0, __uuidof(ID3D11Texture2D), (void **)&texture);
  
      dev->CheckHRESULT(hr);
  
      if(FAILED(hr))
      {
        RDCERR("Failed to get swap chain buffer, HRESULT: %s", ToStr(hr).c_str());
        SAFE_RELEASE(texture);
        return;
      }
    }
    else
    {
      D3D11_TEXTURE2D_DESC texDesc;
  
      texDesc.ArraySize = 1;
      texDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
      texDesc.CPUAccessFlags = 0;
      texDesc.MipLevels = 1;
      texDesc.MiscFlags = 0;
      texDesc.SampleDesc.Count = multisampled ? 4 : 1;
      texDesc.SampleDesc.Quality = 0;
      texDesc.Usage = D3D11_USAGE_DEFAULT;
      texDesc.Width = width;
      texDesc.Height = height;
      texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
  
      hr = dev->CreateTexture2D(&texDesc, NULL, &texture);
    }
  
    hr = dev->CreateRenderTargetView(texture, NULL, &rtv);
  
    dev->CheckHRESULT(hr);
  
    SAFE_RELEASE(texture);
  
    if(FAILED(hr))
    {
      RDCERR("Failed to create RTV for swap chain buffer, HRESULT: %s", ToStr(hr).c_str());
      SAFE_RELEASE(swap);
      return;
    }
  }
  
  void D3D11Replay::OutputWindow::MakeDSV()
  {
    if(!rtv)
      return;
  
    ID3D11Texture2D *texture = NULL;
    {
      ID3D11Resource *res = NULL;
      rtv->GetResource(&res);
      texture = (ID3D11Texture2D *)res;
    }
  
    if(!texture)
    {
      RDCERR("Failed to get swap chain buffer from RTV");
      return;
    }
  
    D3D11_TEXTURE2D_DESC texDesc;
    texture->GetDesc(&texDesc);
  
    SAFE_RELEASE(texture);
  
    texDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
    texDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
  
    HRESULT hr = dev->CreateTexture2D(&texDesc, NULL, &texture);
  
    dev->CheckHRESULT(hr);
  
    if(FAILED(hr))
    {
      RDCERR("Failed to create DSV texture for main output, HRESULT: %s", ToStr(hr).c_str());
      SAFE_RELEASE(swap);
      SAFE_RELEASE(rtv);
      return;
    }
  
    hr = dev->CreateDepthStencilView(texture, NULL, &dsv);
  
    dev->CheckHRESULT(hr);
  
    SAFE_RELEASE(texture);
  
    if(FAILED(hr))
    {
      RDCERR("Failed to create DSV for main output, HRESULT: %s", ToStr(hr).c_str());
      SAFE_RELEASE(swap);
      SAFE_RELEASE(rtv);
      return;
    }
  }
  
  uint64_t D3D11Replay::MakeOutputWindow(WindowingData window, bool depth)
  {
    RDCASSERT(window.system == WindowingSystem::Win32 || window.system == WindowingSystem::Headless,
              window.system);
  
    DXGI_SWAP_CHAIN_DESC swapDesc = {};
    OutputWindow outw = {};
    outw.dev = m_pDevice;
    outw.multisampled = depth;
  
    if(window.system == WindowingSystem::Win32)
    {
      outw.wnd = window.win32.window;
  
      RECT rect = {};
      GetClientRect(outw.wnd, &rect);
  
      swapDesc.BufferCount = 2;
      swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
      outw.width = swapDesc.BufferDesc.Width = rect.right - rect.left;
      outw.height = swapDesc.BufferDesc.Height = rect.bottom - rect.top;
      swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
      swapDesc.SampleDesc.Count = depth ? 4 : 1;
      swapDesc.SampleDesc.Quality = 0;
      swapDesc.OutputWindow = outw.wnd;
      swapDesc.Windowed = TRUE;
      swapDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
      swapDesc.Flags = 0;
  
      HRESULT hr = S_OK;
  
      hr = m_pFactory->CreateSwapChain(m_pDevice, &swapDesc, &outw.swap);
  
      m_pDevice->CheckHRESULT(hr);
  
      if(FAILED(hr))
      {
        RDCERR("Failed to create swap chain for HWND, HRESULT: %s", ToStr(hr).c_str());
        return 0;
      }
    }
    else
    {
      outw.width = window.headless.width;
      outw.height = window.headless.height;
  
      outw.wnd = NULL;
      outw.swap = NULL;
    }
  
    outw.MakeRTV();
  
    outw.dsv = NULL;
    if(depth)
      outw.MakeDSV();
  
    uint64_t id = m_OutputWindowID++;
    m_OutputWindows[id] = outw;
    return id;
  }
  
  void D3D11Replay::DestroyOutputWindow(uint64_t id)
  {
    auto it = m_OutputWindows.find(id);
    if(id == 0 || it == m_OutputWindows.end())
      return;
  
    OutputWindow &outw = it->second;
  
    SAFE_RELEASE(outw.swap);
    SAFE_RELEASE(outw.rtv);
    SAFE_RELEASE(outw.dsv);
  
    m_OutputWindows.erase(it);
  }
  
  bool D3D11Replay::CheckResizeOutputWindow(uint64_t id)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return false;
  
    OutputWindow &outw = m_OutputWindows[id];
  
    if(outw.wnd == NULL || outw.swap == NULL)
      return false;
  
    RECT rect;
    GetClientRect(outw.wnd, &rect);
    long w = rect.right - rect.left;
    long h = rect.bottom - rect.top;
  
    if(w != outw.width || h != outw.height)
    {
      outw.width = w;
      outw.height = h;
  
      D3D11RenderStateTracker tracker(m_pImmediateContext);
  
      m_pImmediateContext->OMSetRenderTargets(0, 0, 0);
  
      if(outw.width > 0 && outw.height > 0)
      {
        SAFE_RELEASE(outw.rtv);
        SAFE_RELEASE(outw.dsv);
  
        DXGI_SWAP_CHAIN_DESC desc;
        outw.swap->GetDesc(&desc);
  
        HRESULT hr = outw.swap->ResizeBuffers(desc.BufferCount, outw.width, outw.height,
                                              desc.BufferDesc.Format, desc.Flags);
  
        m_pDevice->CheckHRESULT(hr);
  
        if(FAILED(hr))
        {
          RDCERR("Failed to resize swap chain, HRESULT: %s", ToStr(hr).c_str());
          return true;
        }
  
        outw.MakeRTV();
        outw.MakeDSV();
      }
  
      return true;
    }
  
    return false;
  }
  
  void D3D11Replay::GetOutputWindowDimensions(uint64_t id, int32_t &w, int32_t &h)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    w = m_OutputWindows[id].width;
    h = m_OutputWindows[id].height;
  }
  
  void D3D11Replay::SetOutputWindowDimensions(uint64_t id, int32_t w, int32_t h)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    OutputWindow &outw = m_OutputWindows[id];
  
    // can't resize an output with an actual window backing
    if(outw.wnd)
      return;
  
    SAFE_RELEASE(outw.rtv);
    SAFE_RELEASE(outw.dsv);
  
    outw.width = w;
    outw.height = h;
  
    outw.MakeRTV();
    outw.MakeDSV();
  }
  
  void D3D11Replay::GetOutputWindowData(uint64_t id, bytebuf &retData)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    OutputWindow &outw = m_OutputWindows[id];
  
    if(!outw.rtv)
      return;
  
    ID3D11Texture2D *texture = NULL;
    {
      ID3D11Resource *res = NULL;
      outw.rtv->GetResource(&res);
      texture = (ID3D11Texture2D *)res;
    }
  
    if(!texture)
    {
      RDCERR("Couldn't get backbuffer texture");
      return;
    }
  
    texture->Release();
  
    ID3D11Texture2D *readback = NULL;
  
    D3D11_TEXTURE2D_DESC texDesc;
    texture->GetDesc(&texDesc);
  
    texDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
    texDesc.BindFlags = 0;
    texDesc.Usage = D3D11_USAGE_STAGING;
    texDesc.SampleDesc.Count = 1;
  
    HRESULT hr = m_pDevice->CreateTexture2D(&texDesc, NULL, &readback);
  
    m_pDevice->CheckHRESULT(hr);
  
    if(FAILED(hr))
    {
      RDCERR("Couldn't create staging texture for readback, HRESULT: %s", ToStr(hr).c_str());
      SAFE_RELEASE(readback);
      return;
    }
  
    ID3D11Texture2D *resolve = NULL;
  
    if(outw.multisampled)
    {
      texDesc.CPUAccessFlags = 0;
      texDesc.Usage = D3D11_USAGE_DEFAULT;
  
      hr = m_pDevice->CreateTexture2D(&texDesc, NULL, &resolve);
  
      m_pDevice->CheckHRESULT(hr);
  
      if(FAILED(hr))
      {
        RDCERR("Couldn't create staging texture for readback, HRESULT: %s", ToStr(hr).c_str());
        SAFE_RELEASE(readback);
        SAFE_RELEASE(resolve);
        return;
      }
    }
  
    ID3D11DeviceContext *ctx = m_pDevice->GetImmediateContext();
  
    if(outw.multisampled)
    {
      ctx->ResolveSubresource(resolve, 0, texture, 0, texDesc.Format);
      ctx->CopyResource(readback, resolve);
      SAFE_RELEASE(resolve);
    }
    else
    {
      ctx->CopyResource(readback, texture);
    }
  
    D3D11_MAPPED_SUBRESOURCE mapped = {};
    ctx->Map(readback, 0, D3D11_MAP_READ, 0, &mapped);
  
    retData.resize(outw.width * outw.height * 3);
  
    byte *src = (byte *)mapped.pData;
    byte *dst = retData.data();
  
    for(int32_t row = 0; row < outw.height; row++)
    {
      for(int32_t x = 0; x < outw.width; x++)
      {
        dst[x * 3 + 0] = src[x * 4 + 0];
        dst[x * 3 + 1] = src[x * 4 + 1];
        dst[x * 3 + 2] = src[x * 4 + 2];
      }
  
      src += mapped.RowPitch;
      dst += outw.width * 3;
    }
  
    ctx->Unmap(readback, 0);
  
    SAFE_RELEASE(readback);
  }
  
  void D3D11Replay::ClearOutputWindowColor(uint64_t id, FloatVector col)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    m_pImmediateContext->ClearRenderTargetView(m_OutputWindows[id].rtv, &col.x);
  }
  
  void D3D11Replay::ClearOutputWindowDepth(uint64_t id, float depth, uint8_t stencil)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    if(m_OutputWindows[id].dsv)
      m_pImmediateContext->ClearDepthStencilView(
          m_OutputWindows[id].dsv, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, depth, stencil);
  }
  
  void D3D11Replay::BindOutputWindow(uint64_t id, bool depth)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    if(m_RealState.active)
      RDCERR("Trashing RealState! Mismatched use of BindOutputWindow / FlipOutputWindow");
  
    m_RealState.active = true;
    m_RealState.state.CopyState(*m_pImmediateContext->GetCurrentPipelineState());
  
    m_pImmediateContext->OMSetRenderTargets(
        1, &m_OutputWindows[id].rtv, depth && m_OutputWindows[id].dsv ? m_OutputWindows[id].dsv : NULL);
  
    D3D11_VIEWPORT viewport = {
        0, 0, (float)m_OutputWindows[id].width, (float)m_OutputWindows[id].height, 0.0f, 1.0f};
    m_pImmediateContext->RSSetViewports(1, &viewport);
  
    SetOutputDimensions(m_OutputWindows[id].width, m_OutputWindows[id].height);
  }
  
  bool D3D11Replay::IsOutputWindowVisible(uint64_t id)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return false;
  
    if(!m_OutputWindows[id].wnd)
      return true;
  
    return (IsWindowVisible(m_OutputWindows[id].wnd) == TRUE);
  }
  
  void D3D11Replay::FlipOutputWindow(uint64_t id)
  {
    if(id == 0 || m_OutputWindows.find(id) == m_OutputWindows.end())
      return;
  
    if(m_OutputWindows[id].swap)
    {
      HRESULT hr = m_OutputWindows[id].swap->Present(0, 0);
  
      m_pDevice->CheckHRESULT(hr);
    }
  
    if(m_RealState.active)
    {
      m_RealState.active = false;
      m_RealState.state.ApplyState(m_pImmediateContext);
      m_RealState.state.Clear();
    }
    else
    {
      RDCERR("RealState wasn't active! Mismatched use of BindOutputWindow / FlipOutputWindow");
    }
  }
  