text: //
  // Copyright (c) 2002-2014 The ANGLE Project Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  //
  
  // renderer9_utils.cpp: Conversion functions and other utility routines
  // specific to the D3D9 renderer.
  
  #include "libANGLE/renderer/d3d/d3d9/renderer9_utils.h"
  
  #include "common/mathutil.h"
  #include "common/debug.h"
  
  #include "libANGLE/formatutils.h"
  #include "libANGLE/Framebuffer.h"
  #include "libANGLE/renderer/d3d/d3d9/formatutils9.h"
  #include "libANGLE/renderer/d3d/d3d9/RenderTarget9.h"
  #include "libANGLE/renderer/d3d/FramebufferD3D.h"
  #include "libANGLE/renderer/driver_utils.h"
  #include "platform/Platform.h"
  #include "platform/WorkaroundsD3D.h"
  
  #include "third_party/systeminfo/SystemInfo.h"
  
  namespace rx
  {
  
  namespace gl_d3d9
  {
  
  D3DCMPFUNC ConvertComparison(GLenum comparison)
  {
      D3DCMPFUNC d3dComp = D3DCMP_ALWAYS;
      switch (comparison)
      {
        case GL_NEVER:    d3dComp = D3DCMP_NEVER;        break;
        case GL_ALWAYS:   d3dComp = D3DCMP_ALWAYS;       break;
        case GL_LESS:     d3dComp = D3DCMP_LESS;         break;
        case GL_LEQUAL:   d3dComp = D3DCMP_LESSEQUAL;    break;
        case GL_EQUAL:    d3dComp = D3DCMP_EQUAL;        break;
        case GL_GREATER:  d3dComp = D3DCMP_GREATER;      break;
        case GL_GEQUAL:   d3dComp = D3DCMP_GREATEREQUAL; break;
        case GL_NOTEQUAL: d3dComp = D3DCMP_NOTEQUAL;     break;
        default: UNREACHABLE();
      }
  
      return d3dComp;
  }
  
  D3DCOLOR ConvertColor(gl::ColorF color)
  {
      return D3DCOLOR_RGBA(gl::unorm<8>(color.red),
                           gl::unorm<8>(color.green),
                           gl::unorm<8>(color.blue),
                           gl::unorm<8>(color.alpha));
  }
  
  D3DBLEND ConvertBlendFunc(GLenum blend)
  {
      D3DBLEND d3dBlend = D3DBLEND_ZERO;
  
      switch (blend)
      {
        case GL_ZERO:                     d3dBlend = D3DBLEND_ZERO;           break;
        case GL_ONE:                      d3dBlend = D3DBLEND_ONE;            break;
        case GL_SRC_COLOR:                d3dBlend = D3DBLEND_SRCCOLOR;       break;
        case GL_ONE_MINUS_SRC_COLOR:      d3dBlend = D3DBLEND_INVSRCCOLOR;    break;
        case GL_DST_COLOR:                d3dBlend = D3DBLEND_DESTCOLOR;      break;
        case GL_ONE_MINUS_DST_COLOR:      d3dBlend = D3DBLEND_INVDESTCOLOR;   break;
        case GL_SRC_ALPHA:                d3dBlend = D3DBLEND_SRCALPHA;       break;
        case GL_ONE_MINUS_SRC_ALPHA:      d3dBlend = D3DBLEND_INVSRCALPHA;    break;
        case GL_DST_ALPHA:                d3dBlend = D3DBLEND_DESTALPHA;      break;
        case GL_ONE_MINUS_DST_ALPHA:      d3dBlend = D3DBLEND_INVDESTALPHA;   break;
        case GL_CONSTANT_COLOR:           d3dBlend = D3DBLEND_BLENDFACTOR;    break;
        case GL_ONE_MINUS_CONSTANT_COLOR: d3dBlend = D3DBLEND_INVBLENDFACTOR; break;
        case GL_CONSTANT_ALPHA:           d3dBlend = D3DBLEND_BLENDFACTOR;    break;
        case GL_ONE_MINUS_CONSTANT_ALPHA: d3dBlend = D3DBLEND_INVBLENDFACTOR; break;
        case GL_SRC_ALPHA_SATURATE:       d3dBlend = D3DBLEND_SRCALPHASAT;    break;
        default: UNREACHABLE();
      }
  
      return d3dBlend;
  }
  
  D3DBLENDOP ConvertBlendOp(GLenum blendOp)
  {
      D3DBLENDOP d3dBlendOp = D3DBLENDOP_ADD;
  
      switch (blendOp)
      {
        case GL_FUNC_ADD:              d3dBlendOp = D3DBLENDOP_ADD;         break;
        case GL_FUNC_SUBTRACT:         d3dBlendOp = D3DBLENDOP_SUBTRACT;    break;
        case GL_FUNC_REVERSE_SUBTRACT: d3dBlendOp = D3DBLENDOP_REVSUBTRACT; break;
        case GL_MIN_EXT:               d3dBlendOp = D3DBLENDOP_MIN;         break;
        case GL_MAX_EXT:               d3dBlendOp = D3DBLENDOP_MAX;         break;
        default: UNREACHABLE();
      }
  
      return d3dBlendOp;
  }
  
  D3DSTENCILOP ConvertStencilOp(GLenum stencilOp)
  {
      D3DSTENCILOP d3dStencilOp = D3DSTENCILOP_KEEP;
  
      switch (stencilOp)
      {
        case GL_ZERO:      d3dStencilOp = D3DSTENCILOP_ZERO;    break;
        case GL_KEEP:      d3dStencilOp = D3DSTENCILOP_KEEP;    break;
        case GL_REPLACE:   d3dStencilOp = D3DSTENCILOP_REPLACE; break;
        case GL_INCR:      d3dStencilOp = D3DSTENCILOP_INCRSAT; break;
        case GL_DECR:      d3dStencilOp = D3DSTENCILOP_DECRSAT; break;
        case GL_INVERT:    d3dStencilOp = D3DSTENCILOP_INVERT;  break;
        case GL_INCR_WRAP: d3dStencilOp = D3DSTENCILOP_INCR;    break;
        case GL_DECR_WRAP: d3dStencilOp = D3DSTENCILOP_DECR;    break;
        default: UNREACHABLE();
      }
  
      return d3dStencilOp;
  }
  
  D3DTEXTUREADDRESS ConvertTextureWrap(GLenum wrap)
  {
      D3DTEXTUREADDRESS d3dWrap = D3DTADDRESS_WRAP;
  
      switch (wrap)
      {
        case GL_REPEAT:            d3dWrap = D3DTADDRESS_WRAP;   break;
        case GL_CLAMP_TO_EDGE:     d3dWrap = D3DTADDRESS_CLAMP;  break;
        case GL_MIRRORED_REPEAT:   d3dWrap = D3DTADDRESS_MIRROR; break;
        default: UNREACHABLE();
      }
  
      return d3dWrap;
  }
  
  D3DCULL ConvertCullMode(gl::CullFaceMode cullFace, GLenum frontFace)
  {
      D3DCULL cull = D3DCULL_CCW;
      switch (cullFace)
      {
          case gl::CullFaceMode::Front:
              cull = (frontFace == GL_CCW ? D3DCULL_CW : D3DCULL_CCW);
              break;
          case gl::CullFaceMode::Back:
              cull = (frontFace == GL_CCW ? D3DCULL_CCW : D3DCULL_CW);
              break;
          case gl::CullFaceMode::FrontAndBack:
              cull = D3DCULL_NONE;  // culling will be handled during draw
              break;
          default:
              UNREACHABLE();
      }
  
      return cull;
  }
  
  D3DCUBEMAP_FACES ConvertCubeFace(gl::TextureTarget cubeFace)
  {
      D3DCUBEMAP_FACES face = D3DCUBEMAP_FACE_POSITIVE_X;
  
      switch (cubeFace)
      {
          case gl::TextureTarget::CubeMapPositiveX:
              face = D3DCUBEMAP_FACE_POSITIVE_X;
              break;
          case gl::TextureTarget::CubeMapNegativeX:
              face = D3DCUBEMAP_FACE_NEGATIVE_X;
              break;
          case gl::TextureTarget::CubeMapPositiveY:
              face = D3DCUBEMAP_FACE_POSITIVE_Y;
              break;
          case gl::TextureTarget::CubeMapNegativeY:
              face = D3DCUBEMAP_FACE_NEGATIVE_Y;
              break;
          case gl::TextureTarget::CubeMapPositiveZ:
              face = D3DCUBEMAP_FACE_POSITIVE_Z;
              break;
          case gl::TextureTarget::CubeMapNegativeZ:
              face = D3DCUBEMAP_FACE_NEGATIVE_Z;
              break;
          default:
              UNREACHABLE();
      }
  
      return face;
  }
  
  DWORD ConvertColorMask(bool red, bool green, bool blue, bool alpha)
  {
      return (red   ? D3DCOLORWRITEENABLE_RED   : 0) |
             (green ? D3DCOLORWRITEENABLE_GREEN : 0) |
             (blue  ? D3DCOLORWRITEENABLE_BLUE  : 0) |
             (alpha ? D3DCOLORWRITEENABLE_ALPHA : 0);
  }
  
  D3DTEXTUREFILTERTYPE ConvertMagFilter(GLenum magFilter, float maxAnisotropy)
  {
      if (maxAnisotropy > 1.0f)
      {
          return D3DTEXF_ANISOTROPIC;
      }
  
      D3DTEXTUREFILTERTYPE d3dMagFilter = D3DTEXF_POINT;
      switch (magFilter)
      {
        case GL_NEAREST: d3dMagFilter = D3DTEXF_POINT;  break;
        case GL_LINEAR:  d3dMagFilter = D3DTEXF_LINEAR; break;
        default: UNREACHABLE();
      }
  
      return d3dMagFilter;
  }
  
  void ConvertMinFilter(GLenum minFilter, D3DTEXTUREFILTERTYPE *d3dMinFilter, D3DTEXTUREFILTERTYPE *d3dMipFilter,
                        float *d3dLodBias, float maxAnisotropy, size_t baseLevel)
  {
      switch (minFilter)
      {
        case GL_NEAREST:
          *d3dMinFilter = D3DTEXF_POINT;
          *d3dMipFilter = D3DTEXF_NONE;
          break;
        case GL_LINEAR:
          *d3dMinFilter = D3DTEXF_LINEAR;
          *d3dMipFilter = D3DTEXF_NONE;
          break;
        case GL_NEAREST_MIPMAP_NEAREST:
          *d3dMinFilter = D3DTEXF_POINT;
          *d3dMipFilter = D3DTEXF_POINT;
          break;
        case GL_LINEAR_MIPMAP_NEAREST:
          *d3dMinFilter = D3DTEXF_LINEAR;
          *d3dMipFilter = D3DTEXF_POINT;
          break;
        case GL_NEAREST_MIPMAP_LINEAR:
          *d3dMinFilter = D3DTEXF_POINT;
          *d3dMipFilter = D3DTEXF_LINEAR;
          break;
        case GL_LINEAR_MIPMAP_LINEAR:
          *d3dMinFilter = D3DTEXF_LINEAR;
          *d3dMipFilter = D3DTEXF_LINEAR;
          break;
        default:
          *d3dMinFilter = D3DTEXF_POINT;
          *d3dMipFilter = D3DTEXF_NONE;
          UNREACHABLE();
      }
  
      // Disabling mipmapping will always sample from level 0 of the texture. It is possible to work
      // around this by modifying D3DSAMP_MAXMIPLEVEL to force a specific mip level to become the
      // lowest sampled mip level and using a large negative value for D3DSAMP_MIPMAPLODBIAS to
      // ensure that only the base mip level is sampled.
      if (baseLevel > 0 && *d3dMipFilter == D3DTEXF_NONE)
      {
          *d3dMipFilter = D3DTEXF_POINT;
          *d3dLodBias = -static_cast<float>(gl::IMPLEMENTATION_MAX_TEXTURE_LEVELS);
      }
      else
      {
          *d3dLodBias = 0.0f;
      }
  
      if (maxAnisotropy > 1.0f)
      {
          *d3dMinFilter = D3DTEXF_ANISOTROPIC;
      }
  }
  
  D3DQUERYTYPE ConvertQueryType(gl::QueryType type)
  {
      switch (type)
      {
          case gl::QueryType::AnySamples:
          case gl::QueryType::AnySamplesConservative:
              return D3DQUERYTYPE_OCCLUSION;
          case gl::QueryType::CommandsCompleted:
              return D3DQUERYTYPE_EVENT;
          default:
              UNREACHABLE();
              return static_cast<D3DQUERYTYPE>(0);
      }
  }
  
  D3DMULTISAMPLE_TYPE GetMultisampleType(GLuint samples)
  {
      return (samples > 1) ? static_cast<D3DMULTISAMPLE_TYPE>(samples) : D3DMULTISAMPLE_NONE;
  }
  
  }
  
  namespace d3d9_gl
  {
  
  unsigned int GetReservedVaryingVectors()
  {
      // We reserve two registers for "dx_Position" and "gl_Position". The spec says they
      // don't count towards the varying limit, so we must make space for them. We also
      // reserve the last register since it can only pass a PSIZE, and not any arbitrary
      // varying.
      return 3;
  }
  
  unsigned int GetReservedVertexUniformVectors()
  {
      return 3;  // dx_ViewCoords, dx_ViewAdjust and dx_DepthRange.
  }
  
  unsigned int GetReservedFragmentUniformVectors()
  {
      return 3;  // dx_ViewCoords, dx_DepthFront and dx_DepthRange.
  }
  
  GLsizei GetSamplesCount(D3DMULTISAMPLE_TYPE type)
  {
      return (type != D3DMULTISAMPLE_NONMASKABLE) ? type : 0;
  }
  
  bool IsFormatChannelEquivalent(D3DFORMAT d3dformat, GLenum format)
  {
      GLenum internalFormat  = d3d9::GetD3DFormatInfo(d3dformat).info().glInternalFormat;
      GLenum convertedFormat = gl::GetSizedInternalFormatInfo(internalFormat).format;
      return convertedFormat == format;
  }
  
  static gl::TextureCaps GenerateTextureFormatCaps(GLenum internalFormat, IDirect3D9 *d3d9, D3DDEVTYPE deviceType,
                                                   UINT adapter, D3DFORMAT adapterFormat)
  {
      gl::TextureCaps textureCaps;
  
      const d3d9::TextureFormat &d3dFormatInfo = d3d9::GetTextureFormatInfo(internalFormat);
      const gl::InternalFormat &formatInfo     = gl::GetSizedInternalFormatInfo(internalFormat);
  
      if (d3dFormatInfo.texFormat != D3DFMT_UNKNOWN)
      {
          if (formatInfo.depthBits > 0 || formatInfo.stencilBits > 0)
          {
              textureCaps.texturable = SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, adapterFormat, 0, D3DRTYPE_TEXTURE, d3dFormatInfo.texFormat));
          }
          else
          {
              textureCaps.texturable = SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, adapterFormat, 0, D3DRTYPE_TEXTURE, d3dFormatInfo.texFormat)) &&
                                       SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, adapterFormat, 0, D3DRTYPE_CUBETEXTURE, d3dFormatInfo.texFormat));
          }
  
          textureCaps.filterable = SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, adapterFormat, D3DUSAGE_QUERY_FILTER, D3DRTYPE_TEXTURE, d3dFormatInfo.texFormat));
      }
  
      if (d3dFormatInfo.renderFormat != D3DFMT_UNKNOWN)
      {
          textureCaps.renderable = SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, adapterFormat, D3DUSAGE_RENDERTARGET, D3DRTYPE_TEXTURE, d3dFormatInfo.renderFormat));
  
          if ((formatInfo.depthBits > 0 || formatInfo.stencilBits > 0) && !textureCaps.renderable)
          {
              textureCaps.renderable = SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, adapterFormat, D3DUSAGE_DEPTHSTENCIL, D3DRTYPE_TEXTURE, d3dFormatInfo.renderFormat));
          }
  
          textureCaps.sampleCounts.insert(1);
          for (unsigned int i = D3DMULTISAMPLE_2_SAMPLES; i <= D3DMULTISAMPLE_16_SAMPLES; i++)
          {
              D3DMULTISAMPLE_TYPE multisampleType = D3DMULTISAMPLE_TYPE(i);
  
              HRESULT result = d3d9->CheckDeviceMultiSampleType(
                  adapter, deviceType, d3dFormatInfo.renderFormat, TRUE, multisampleType, nullptr);
              if (SUCCEEDED(result))
              {
                  textureCaps.sampleCounts.insert(i);
              }
          }
      }
  
      return textureCaps;
  }
  
  void GenerateCaps(IDirect3D9 *d3d9,
                    IDirect3DDevice9 *device,
                    D3DDEVTYPE deviceType,
                    UINT adapter,
                    gl::Caps *caps,
                    gl::TextureCapsMap *textureCapsMap,
                    gl::Extensions *extensions,
                    gl::Limitations *limitations)
  {
      D3DCAPS9 deviceCaps;
      if (FAILED(d3d9->GetDeviceCaps(adapter, deviceType, &deviceCaps)))
      {
          // Can't continue with out device caps
          return;
      }
  
      D3DDISPLAYMODE currentDisplayMode;
      d3d9->GetAdapterDisplayMode(adapter, &currentDisplayMode);
  
      GLuint maxSamples = 0;
      for (GLenum internalFormat : gl::GetAllSizedInternalFormats())
      {
          gl::TextureCaps textureCaps = GenerateTextureFormatCaps(internalFormat, d3d9, deviceType,
                                                                  adapter, currentDisplayMode.Format);
          textureCapsMap->insert(internalFormat, textureCaps);
  
          maxSamples = std::max(maxSamples, textureCaps.getMaxSamples());
  
          if (gl::GetSizedInternalFormatInfo(internalFormat).compressed)
          {
              caps->compressedTextureFormats.push_back(internalFormat);
          }
      }
  
      // GL core feature limits
      caps->maxElementIndex = static_cast<GLint64>(std::numeric_limits<unsigned int>::max());
  
      // 3D textures are unimplemented in D3D9
      caps->max3DTextureSize = 1;
  
      // Only one limit in GL, use the minimum dimension
      caps->max2DTextureSize = std::min(deviceCaps.MaxTextureWidth, deviceCaps.MaxTextureHeight);
  
      // D3D treats cube maps as a special case of 2D textures
      caps->maxCubeMapTextureSize = caps->max2DTextureSize;
  
      // Array textures are not available in D3D9
      caps->maxArrayTextureLayers = 1;
  
      // ES3-only feature
      caps->maxLODBias = 0.0f;
  
      // No specific limits on render target size, maximum 2D texture size is equivalent
      caps->maxRenderbufferSize = caps->max2DTextureSize;
  
      // Draw buffers are not supported in D3D9
      caps->maxDrawBuffers = 1;
      caps->maxColorAttachments = 1;
  
      // No specific limits on viewport size, maximum 2D texture size is equivalent
      caps->maxViewportWidth = caps->max2DTextureSize;
      caps->maxViewportHeight = caps->maxViewportWidth;
  
      // Point size is clamped to 1.0f when the shader model is less than 3
      caps->minAliasedPointSize = 1.0f;
      caps->maxAliasedPointSize = ((D3DSHADER_VERSION_MAJOR(deviceCaps.PixelShaderVersion) >= 3) ? deviceCaps.MaxPointSize : 1.0f);
  
      // Wide lines not supported
      caps->minAliasedLineWidth = 1.0f;
      caps->maxAliasedLineWidth = 1.0f;
  
      // Primitive count limits (unused in ES2)
      caps->maxElementsIndices = 0;
      caps->maxElementsVertices = 0;
  
      // Program and shader binary formats (no supported shader binary formats)
      caps->programBinaryFormats.push_back(GL_PROGRAM_BINARY_ANGLE);
  
      caps->vertexHighpFloat.setIEEEFloat();
      caps->vertexMediumpFloat.setIEEEFloat();
      caps->vertexLowpFloat.setIEEEFloat();
      caps->fragmentHighpFloat.setIEEEFloat();
      caps->fragmentMediumpFloat.setIEEEFloat();
      caps->fragmentLowpFloat.setIEEEFloat();
  
      // Some (most) hardware only supports single-precision floating-point numbers,
      // which can accurately represent integers up to +/-16777216
      caps->vertexHighpInt.setSimulatedInt(24);
      caps->vertexMediumpInt.setSimulatedInt(24);
      caps->vertexLowpInt.setSimulatedInt(24);
      caps->fragmentHighpInt.setSimulatedInt(24);
      caps->fragmentMediumpInt.setSimulatedInt(24);
      caps->fragmentLowpInt.setSimulatedInt(24);
  
      // WaitSync is ES3-only, set to zero
      caps->maxServerWaitTimeout = 0;
  
      // Vertex shader limits
      caps->maxVertexAttributes = 16;
      // Vertex Attrib Binding not supported.
      caps->maxVertexAttribBindings = caps->maxVertexAttributes;
  
      const size_t MAX_VERTEX_CONSTANT_VECTORS_D3D9 = 256;
      caps->maxVertexUniformVectors =
          MAX_VERTEX_CONSTANT_VECTORS_D3D9 - GetReservedVertexUniformVectors();
      caps->maxVertexUniformComponents = caps->maxVertexUniformVectors * 4;
  
      caps->maxShaderUniformBlocks[gl::ShaderType::Vertex] = 0;
  
      // SM3 only supports 12 output variables, but the special 12th register is only for PSIZE.
      const unsigned int MAX_VERTEX_OUTPUT_VECTORS_SM3 = 12 - GetReservedVaryingVectors();
      const unsigned int MAX_VERTEX_OUTPUT_VECTORS_SM2 = 10 - GetReservedVaryingVectors();
      caps->maxVertexOutputComponents = ((deviceCaps.VertexShaderVersion >= D3DVS_VERSION(3, 0)) ? MAX_VERTEX_OUTPUT_VECTORS_SM3
                                                                                                 : MAX_VERTEX_OUTPUT_VECTORS_SM2) * 4;
  
      // Only Direct3D 10 ready devices support all the necessary vertex texture formats.
      // We test this using D3D9 by checking support for the R16F format.
      if (deviceCaps.VertexShaderVersion >= D3DVS_VERSION(3, 0) &&
          SUCCEEDED(d3d9->CheckDeviceFormat(adapter, deviceType, currentDisplayMode.Format,
                                            D3DUSAGE_QUERY_VERTEXTEXTURE, D3DRTYPE_TEXTURE, D3DFMT_R16F)))
      {
          const size_t MAX_TEXTURE_IMAGE_UNITS_VTF_SM3 = 4;
          caps->maxShaderTextureImageUnits[gl::ShaderType::Vertex] = MAX_TEXTURE_IMAGE_UNITS_VTF_SM3;
      }
      else
      {
          caps->maxShaderTextureImageUnits[gl::ShaderType::Vertex] = 0;
      }
  
      // Fragment shader limits
      const size_t MAX_PIXEL_CONSTANT_VECTORS_SM3 = 224;
      const size_t MAX_PIXEL_CONSTANT_VECTORS_SM2 = 32;
      caps->maxFragmentUniformVectors =
          ((deviceCaps.PixelShaderVersion >= D3DPS_VERSION(3, 0)) ? MAX_PIXEL_CONSTANT_VECTORS_SM3
                                                                  : MAX_PIXEL_CONSTANT_VECTORS_SM2) -
          GetReservedFragmentUniformVectors();
      caps->maxFragmentUniformComponents = caps->maxFragmentUniformVectors * 4;
      caps->maxShaderUniformBlocks[gl::ShaderType::Fragment]     = 0;
      caps->maxFragmentInputComponents = caps->maxVertexOutputComponents;
      caps->maxShaderTextureImageUnits[gl::ShaderType::Fragment] = 16;
      caps->minProgramTexelOffset = 0;
      caps->maxProgramTexelOffset = 0;
  
      // Aggregate shader limits (unused in ES2)
      caps->maxUniformBufferBindings = 0;
      caps->maxUniformBlockSize = 0;
      caps->uniformBufferOffsetAlignment = 0;
      caps->maxCombinedUniformBlocks = 0;
      caps->maxCombinedVertexUniformComponents = 0;
      caps->maxCombinedFragmentUniformComponents = 0;
      caps->maxVaryingComponents = 0;
  
      // Aggregate shader limits
      caps->maxVaryingVectors = caps->maxVertexOutputComponents / 4;
      caps->maxCombinedTextureImageUnits = caps->maxShaderTextureImageUnits[gl::ShaderType::Vertex] +
                                           caps->maxShaderTextureImageUnits[gl::ShaderType::Fragment];
  
      // Transform feedback limits
      caps->maxTransformFeedbackInterleavedComponents = 0;
      caps->maxTransformFeedbackSeparateAttributes = 0;
      caps->maxTransformFeedbackSeparateComponents = 0;
  
      // Multisample limits
      caps->maxSamples = maxSamples;
  
      // GL extension support
      extensions->setTextureExtensionSupport(*textureCapsMap);
      extensions->elementIndexUint = deviceCaps.MaxVertexIndex >= (1 << 16);
      extensions->getProgramBinary = true;
      extensions->rgb8rgba8 = true;
      extensions->readFormatBGRA = true;
      extensions->pixelBufferObject = false;
      extensions->mapBuffer = false;
      extensions->mapBufferRange = false;
  
      // textureRG is emulated and not performant.
      extensions->textureRG = false;
  
      D3DADAPTER_IDENTIFIER9 adapterId = {};
      if (SUCCEEDED(d3d9->GetAdapterIdentifier(adapter, 0, &adapterId)))
      {
          // ATI cards on XP have problems with non-power-of-two textures.
          extensions->textureNPOT = !(deviceCaps.TextureCaps & D3DPTEXTURECAPS_POW2) &&
                                    !(deviceCaps.TextureCaps & D3DPTEXTURECAPS_CUBEMAP_POW2) &&
                                    !(deviceCaps.TextureCaps & D3DPTEXTURECAPS_NONPOW2CONDITIONAL) &&
                                    !(!isWindowsVistaOrGreater() && IsAMD(adapterId.VendorId));
  
          // Disable depth texture support on AMD cards (See ANGLE issue 839)
          if (IsAMD(adapterId.VendorId))
          {
              extensions->depthTextures = false;
          }
      }
      else
      {
          extensions->textureNPOT = false;
      }
  
      extensions->drawBuffers = false;
      extensions->textureStorage = true;
  
      // Must support a minimum of 2:1 anisotropy for max anisotropy to be considered supported, per the spec
      extensions->textureFilterAnisotropic = (deviceCaps.RasterCaps & D3DPRASTERCAPS_ANISOTROPY) != 0 && deviceCaps.MaxAnisotropy >= 2;
      extensions->maxTextureAnisotropy = static_cast<GLfloat>(deviceCaps.MaxAnisotropy);
  
      // Check occlusion query support by trying to create one
      IDirect3DQuery9 *occlusionQuery = nullptr;
      extensions->occlusionQueryBoolean = SUCCEEDED(device->CreateQuery(D3DQUERYTYPE_OCCLUSION, &occlusionQuery)) && occlusionQuery;
      SafeRelease(occlusionQuery);
  
      // Check event query support by trying to create one
      IDirect3DQuery9 *eventQuery = nullptr;
      extensions->fence = SUCCEEDED(device->CreateQuery(D3DQUERYTYPE_EVENT, &eventQuery)) && eventQuery;
      SafeRelease(eventQuery);
  
      extensions->disjointTimerQuery     = false;
      extensions->robustness = true;
      // It seems that only DirectX 10 and higher enforce the well-defined behavior of always
      // returning zero values when out-of-bounds reads. See
      // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_robustness.txt
      extensions->robustBufferAccessBehavior = false;
      extensions->blendMinMax = true;
      extensions->framebufferBlit = true;
      extensions->framebufferMultisample = true;
      extensions->instancedArrays = deviceCaps.PixelShaderVersion >= D3DPS_VERSION(3, 0);
      extensions->packReverseRowOrder = true;
      extensions->standardDerivatives = (deviceCaps.PS20Caps.Caps & D3DPS20CAPS_GRADIENTINSTRUCTIONS) != 0;
      extensions->shaderTextureLOD = true;
      extensions->fragDepth = true;
      extensions->textureUsage = true;
      extensions->translatedShaderSource = true;
      extensions->fboRenderMipmap = false;
      extensions->discardFramebuffer = false; // It would be valid to set this to true, since glDiscardFramebufferEXT is just a hint
      extensions->colorBufferFloat = false;
      extensions->debugMarker = true;
      extensions->eglImage               = true;
      extensions->eglImageExternal       = true;
      extensions->unpackSubimage         = true;
      extensions->packSubimage           = true;
      extensions->syncQuery              = extensions->fence;
      extensions->copyTexture            = true;
  
      // D3D9 has no concept of separate masks and refs for front and back faces in the depth stencil
      // state.
      limitations->noSeparateStencilRefsAndMasks = true;
  
      // D3D9 shader models have limited support for looping, so the Appendix A
      // index/loop limitations are necessary. Workarounds that are needed to
      // support dynamic indexing of vectors on HLSL also don't work on D3D9.
      limitations->shadersRequireIndexedLoopValidation = true;
  
      // D3D9 cannot support constant color and alpha blend funcs together
      limitations->noSimultaneousConstantColorAndAlphaBlendFunc = true;
  
      // D3D9 cannot support packing more than one variable to a single varying.
      // TODO(jmadill): Implement more sophisticated component packing in D3D9.
      limitations->noFlexibleVaryingPacking = true;
  }
  
  }
  
  namespace d3d9
  {
  
  GLuint ComputeBlockSize(D3DFORMAT format, GLuint width, GLuint height)
  {
      const D3DFormat &d3dFormatInfo = d3d9::GetD3DFormatInfo(format);
      GLuint numBlocksWide = (width + d3dFormatInfo.blockWidth - 1) / d3dFormatInfo.blockWidth;
      GLuint numBlocksHight = (height + d3dFormatInfo.blockHeight - 1) / d3dFormatInfo.blockHeight;
      return (d3dFormatInfo.pixelBytes * numBlocksWide * numBlocksHight);
  }
  
  void MakeValidSize(bool isImage, D3DFORMAT format, GLsizei *requestWidth, GLsizei *requestHeight, int *levelOffset)
  {
      const D3DFormat &d3dFormatInfo = d3d9::GetD3DFormatInfo(format);
  
      int upsampleCount = 0;
      // Don't expand the size of full textures that are at least (blockWidth x blockHeight) already.
      if (isImage || *requestWidth < static_cast<GLsizei>(d3dFormatInfo.blockWidth) ||
          *requestHeight < static_cast<GLsizei>(d3dFormatInfo.blockHeight))
      {
          while (*requestWidth % d3dFormatInfo.blockWidth != 0 || *requestHeight % d3dFormatInfo.blockHeight != 0)
          {
              *requestWidth <<= 1;
              *requestHeight <<= 1;
              upsampleCount++;
          }
      }
      *levelOffset = upsampleCount;
  }
  
  angle::WorkaroundsD3D GenerateWorkarounds()
  {
      angle::WorkaroundsD3D workarounds;
      workarounds.mrtPerfWorkaround = true;
      workarounds.setDataFasterThanImageUpload = false;
      workarounds.useInstancedPointSpriteEmulation = false;
  
      // TODO(jmadill): Disable workaround when we have a fixed compiler DLL.
      workarounds.expandIntegerPowExpressions = true;
  
      // Call platform hooks for testing overrides.
      auto *platform = ANGLEPlatformCurrent();
      platform->overrideWorkaroundsD3D(platform, &workarounds);
  
      return workarounds;
  }
  
  }  // namespace d3d9
  
  }  // namespace rx
  