Rendertarget attachments, texture and image changes (#358)

* Add multiple color outputs for fragment shaders

* Add registers and gal enums

* Use textures for framebuffers and split color and zeta framebuffers

* Abstract texture and framebuffer targets as an image

* Share images between framebuffers and textures

* Unstub formats

* Add some formats

* Disable multiple attachments

* Cache framebuffer attachments

* Handle format types

* Add some rendertarget formats

* Code cleanup

* Fixup half float types

* Address feedback

* Disable multiple attachments in shaders

* Add A4B4G4R4 image format

* Add reversed section for image enums
This commit is contained in:
ReinUsesLisp 2018-08-19 22:25:26 -03:00 committed by gdkchan
parent 056c2840b1
commit 726de8c46a
25 changed files with 1360 additions and 441 deletions

View file

@ -0,0 +1,124 @@
using OpenTK.Graphics.OpenGL;
using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class ImageHandler
{
//TODO: Use a variable value here
public const int MaxBpp = 16;
private static int CopyBuffer = 0;
private static int CopyBufferSize = 0;
public GalImage Image { get; private set; }
public int Width => Image.Width;
public int Height => Image.Height;
public GalImageFormat Format => Image.Format;
public PixelInternalFormat InternalFormat { get; private set; }
public PixelFormat PixelFormat { get; private set; }
public PixelType PixelType { get; private set; }
public int Handle { get; private set; }
private bool Initialized;
public ImageHandler()
{
Handle = GL.GenTexture();
}
public ImageHandler(int Handle, GalImage Image)
{
this.Handle = Handle;
this.Image = Image;
}
public void EnsureSetup(GalImage Image)
{
if (Width != Image.Width ||
Height != Image.Height ||
Format != Image.Format ||
!Initialized)
{
(PixelInternalFormat InternalFormat, PixelFormat PixelFormat, PixelType PixelType) =
OGLEnumConverter.GetImageFormat(Image.Format);
GL.BindTexture(TextureTarget.Texture2D, Handle);
if (Initialized)
{
if (CopyBuffer == 0)
{
CopyBuffer = GL.GenBuffer();
}
int MaxWidth = Math.Max(Image.Width, Width);
int MaxHeight = Math.Max(Image.Height, Height);
int CurrentSize = MaxWidth * MaxHeight * MaxBpp;
GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyBuffer);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyBuffer);
if (CopyBufferSize < CurrentSize)
{
CopyBufferSize = CurrentSize;
GL.BufferData(BufferTarget.PixelPackBuffer, CurrentSize, IntPtr.Zero, BufferUsageHint.StreamCopy);
}
GL.GetTexImage(TextureTarget.Texture2D, 0, this.PixelFormat, this.PixelType, IntPtr.Zero);
GL.DeleteTexture(Handle);
Handle = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, Handle);
}
const int MinFilter = (int)TextureMinFilter.Linear;
const int MagFilter = (int)TextureMagFilter.Linear;
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter);
const int Level = 0;
const int Border = 0;
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
InternalFormat,
Image.Width,
Image.Height,
Border,
PixelFormat,
PixelType,
IntPtr.Zero);
if (Initialized)
{
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
}
this.Image = Image;
this.InternalFormat = InternalFormat;
this.PixelFormat = PixelFormat;
this.PixelType = PixelType;
Initialized = true;
}
}
public bool HasColor { get => ImageFormatConverter.HasColor(Format); }
public bool HasDepth { get => ImageFormatConverter.HasDepth(Format); }
public bool HasStencil { get => ImageFormatConverter.HasStencil(Format); }
}
}

View file

@ -36,12 +36,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void SetData(long Key, long Size, IntPtr HostAddress)
{
if (!Cache.TryGetValue(Key, out OGLStreamBuffer Buffer))
if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer))
{
throw new InvalidOperationException();
Buffer.SetData(Size, HostAddress);
}
Buffer.SetData(Size, HostAddress);
}
public bool TryGetUbo(long Key, out int UboHandle)

View file

@ -125,40 +125,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
throw new ArgumentException(nameof(Type));
}
public static (PixelFormat, PixelType) GetTextureFormat(GalTextureFormat Format)
public static (PixelInternalFormat, PixelFormat, PixelType) GetImageFormat(GalImageFormat Format)
{
switch (Format)
{
case GalTextureFormat.R32G32B32A32: return (PixelFormat.Rgba, PixelType.Float);
case GalTextureFormat.R16G16B16A16: return (PixelFormat.Rgba, PixelType.HalfFloat);
case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte);
case GalTextureFormat.A2B10G10R10: return (PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed);
case GalTextureFormat.R32: return (PixelFormat.Red, PixelType.Float);
case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551);
case GalTextureFormat.B5G6R5: return (PixelFormat.Rgb, PixelType.UnsignedShort565);
case GalTextureFormat.G8R8: return (PixelFormat.Rg, PixelType.UnsignedByte);
case GalTextureFormat.R16: return (PixelFormat.Red, PixelType.HalfFloat);
case GalTextureFormat.R8: return (PixelFormat.Red, PixelType.UnsignedByte);
case GalTextureFormat.ZF32: return (PixelFormat.DepthComponent, PixelType.Float);
case GalTextureFormat.BF10GF11RF11: return (PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev);
case GalTextureFormat.Z24S8: return (PixelFormat.DepthStencil, PixelType.UnsignedInt248);
case GalImageFormat.R32G32B32A32_SFLOAT: return (PixelInternalFormat.Rgba32f, PixelFormat.Rgba, PixelType.Float);
case GalImageFormat.R32G32B32A32_SINT: return (PixelInternalFormat.Rgba32i, PixelFormat.RgbaInteger, PixelType.Int);
case GalImageFormat.R32G32B32A32_UINT: return (PixelInternalFormat.Rgba32ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt);
case GalImageFormat.R16G16B16A16_SFLOAT: return (PixelInternalFormat.Rgba16f, PixelFormat.Rgba, PixelType.HalfFloat);
case GalImageFormat.R16G16B16A16_SINT: return (PixelInternalFormat.Rgba16i, PixelFormat.RgbaInteger, PixelType.Short);
case GalImageFormat.R16G16B16A16_UINT: return (PixelInternalFormat.Rgba16ui, PixelFormat.RgbaInteger, PixelType.UnsignedShort);
case GalImageFormat.A8B8G8R8_SNORM_PACK32: return (PixelInternalFormat.Rgba8Snorm, PixelFormat.Rgba, PixelType.Byte);
case GalImageFormat.A8B8G8R8_UNORM_PACK32: return (PixelInternalFormat.Rgba8, PixelFormat.Rgba, PixelType.UnsignedByte);
case GalImageFormat.A8B8G8R8_SINT_PACK32: return (PixelInternalFormat.Rgba8i, PixelFormat.RgbaInteger, PixelType.Byte);
case GalImageFormat.A8B8G8R8_UINT_PACK32: return (PixelInternalFormat.Rgba8ui, PixelFormat.RgbaInteger, PixelType.UnsignedByte);
case GalImageFormat.A8B8G8R8_SRGB_PACK32: return (PixelInternalFormat.Srgb8Alpha8, PixelFormat.Rgba, PixelType.UnsignedByte);
case GalImageFormat.A2B10G10R10_UINT_PACK32: return (PixelInternalFormat.Rgb10A2ui, PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed);
case GalImageFormat.A2B10G10R10_UNORM_PACK32: return (PixelInternalFormat.Rgb10A2, PixelFormat.Rgba, PixelType.UnsignedInt2101010Reversed);
case GalImageFormat.R32_SFLOAT: return (PixelInternalFormat.R32f, PixelFormat.Red, PixelType.Float);
case GalImageFormat.R32_SINT: return (PixelInternalFormat.R32i, PixelFormat.Red, PixelType.Int);
case GalImageFormat.R32_UINT: return (PixelInternalFormat.R32ui, PixelFormat.Red, PixelType.UnsignedInt);
case GalImageFormat.A1R5G5B5_UNORM_PACK16: return (PixelInternalFormat.Rgb5A1, PixelFormat.Rgba, PixelType.UnsignedShort5551);
case GalImageFormat.B5G6R5_UNORM_PACK16: return (PixelInternalFormat.Rgba, PixelFormat.Rgb, PixelType.UnsignedShort565);
case GalImageFormat.R16G16_SFLOAT: return (PixelInternalFormat.Rg16f, PixelFormat.Rg, PixelType.HalfFloat);
case GalImageFormat.R16G16_SINT: return (PixelInternalFormat.Rg16i, PixelFormat.RgInteger, PixelType.Short);
case GalImageFormat.R16G16_SNORM: return (PixelInternalFormat.Rg16Snorm, PixelFormat.Rg, PixelType.Byte);
case GalImageFormat.R16G16_UNORM: return (PixelInternalFormat.Rg16, PixelFormat.Rg, PixelType.UnsignedShort);
case GalImageFormat.R8G8_SINT: return (PixelInternalFormat.Rg8i, PixelFormat.RgInteger, PixelType.Byte);
case GalImageFormat.R8G8_SNORM: return (PixelInternalFormat.Rg8Snorm, PixelFormat.Rg, PixelType.Byte);
case GalImageFormat.R8G8_UINT: return (PixelInternalFormat.Rg8ui, PixelFormat.RgInteger, PixelType.UnsignedByte);
case GalImageFormat.R8G8_UNORM: return (PixelInternalFormat.Rg8, PixelFormat.Rg, PixelType.UnsignedByte);
case GalImageFormat.R16_SFLOAT: return (PixelInternalFormat.R16f, PixelFormat.Red, PixelType.HalfFloat);
case GalImageFormat.R16_SINT: return (PixelInternalFormat.R16i, PixelFormat.RedInteger, PixelType.Short);
case GalImageFormat.R16_SNORM: return (PixelInternalFormat.R16Snorm, PixelFormat.Red, PixelType.Byte);
case GalImageFormat.R16_UINT: return (PixelInternalFormat.R16ui, PixelFormat.RedInteger, PixelType.UnsignedShort);
case GalImageFormat.R16_UNORM: return (PixelInternalFormat.R16, PixelFormat.Red, PixelType.UnsignedShort);
case GalImageFormat.R8_SINT: return (PixelInternalFormat.R8i, PixelFormat.RedInteger, PixelType.Byte);
case GalImageFormat.R8_SNORM: return (PixelInternalFormat.R8Snorm, PixelFormat.Red, PixelType.Byte);
case GalImageFormat.R8_UINT: return (PixelInternalFormat.R8ui, PixelFormat.RedInteger, PixelType.UnsignedByte);
case GalImageFormat.R8_UNORM: return (PixelInternalFormat.R8, PixelFormat.Red, PixelType.UnsignedByte);
case GalImageFormat.B10G11R11_UFLOAT_PACK32: return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb, PixelType.UnsignedInt10F11F11FRev);
case GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED: return (PixelInternalFormat.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed);
case GalImageFormat.D24_UNORM_S8_UINT: return (PixelInternalFormat.Depth24Stencil8, PixelFormat.DepthStencil, PixelType.UnsignedInt248);
case GalImageFormat.D32_SFLOAT: return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float);
case GalImageFormat.D16_UNORM: return (PixelInternalFormat.DepthComponent16, PixelFormat.DepthComponent, PixelType.UnsignedShort);
}
throw new NotImplementedException(Format.ToString());
}
public static InternalFormat GetCompressedTextureFormat(GalTextureFormat Format)
public static InternalFormat GetCompressedImageFormat(GalImageFormat Format)
{
switch (Format)
{
case GalTextureFormat.BC6H_UF16: return InternalFormat.CompressedRgbBptcUnsignedFloat;
case GalTextureFormat.BC6H_SF16: return InternalFormat.CompressedRgbBptcSignedFloat;
case GalTextureFormat.BC7U: return InternalFormat.CompressedRgbaBptcUnorm;
case GalTextureFormat.BC1: return InternalFormat.CompressedRgbaS3tcDxt1Ext;
case GalTextureFormat.BC2: return InternalFormat.CompressedRgbaS3tcDxt3Ext;
case GalTextureFormat.BC3: return InternalFormat.CompressedRgbaS3tcDxt5Ext;
case GalTextureFormat.BC4: return InternalFormat.CompressedRedRgtc1;
case GalTextureFormat.BC5: return InternalFormat.CompressedRgRgtc2;
case GalImageFormat.BC6H_UFLOAT_BLOCK: return InternalFormat.CompressedRgbBptcUnsignedFloat;
case GalImageFormat.BC6H_SFLOAT_BLOCK: return InternalFormat.CompressedRgbBptcSignedFloat;
case GalImageFormat.BC7_UNORM_BLOCK: return InternalFormat.CompressedRgbaBptcUnorm;
case GalImageFormat.BC1_RGBA_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt1Ext;
case GalImageFormat.BC2_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt3Ext;
case GalImageFormat.BC3_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt5Ext;
case GalImageFormat.BC4_SNORM_BLOCK: return InternalFormat.CompressedSignedRedRgtc1;
case GalImageFormat.BC4_UNORM_BLOCK: return InternalFormat.CompressedRedRgtc1;
case GalImageFormat.BC5_SNORM_BLOCK: return InternalFormat.CompressedSignedRgRgtc2;
case GalImageFormat.BC5_UNORM_BLOCK: return InternalFormat.CompressedRgRgtc2;
}
throw new NotImplementedException(Format.ToString());

View file

@ -1,10 +1,9 @@
using OpenTK.Graphics.OpenGL;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.OpenGL
{
public class OGLFrameBuffer : IGalFrameBuffer
class OGLFrameBuffer : IGalFrameBuffer
{
private struct Rect
{
@ -15,50 +14,38 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public Rect(int X, int Y, int Width, int Height)
{
this.X = X;
this.Y = Y;
this.Width = Width;
this.X = X;
this.Y = Y;
this.Width = Width;
this.Height = Height;
}
}
private class FrameBuffer
private static readonly DrawBuffersEnum[] DrawBuffers = new DrawBuffersEnum[]
{
public int Width { get; set; }
public int Height { get; set; }
public int Handle { get; private set; }
public int RbHandle { get; private set; }
public int TexHandle { get; private set; }
public FrameBuffer(int Width, int Height, bool HasRenderBuffer)
{
this.Width = Width;
this.Height = Height;
Handle = GL.GenFramebuffer();
TexHandle = GL.GenTexture();
if (HasRenderBuffer)
{
RbHandle = GL.GenRenderbuffer();
}
}
}
DrawBuffersEnum.ColorAttachment0,
DrawBuffersEnum.ColorAttachment1,
DrawBuffersEnum.ColorAttachment2,
DrawBuffersEnum.ColorAttachment3,
DrawBuffersEnum.ColorAttachment4,
DrawBuffersEnum.ColorAttachment5,
DrawBuffersEnum.ColorAttachment6,
DrawBuffersEnum.ColorAttachment7,
};
private const int NativeWidth = 1280;
private const int NativeHeight = 720;
private Dictionary<long, FrameBuffer> Fbs;
private const GalImageFormat RawFormat = GalImageFormat.A8B8G8R8_UNORM_PACK32;
private OGLTexture Texture;
private ImageHandler RawTex;
private ImageHandler ReadTex;
private Rect Viewport;
private Rect Window;
private FrameBuffer CurrFb;
private FrameBuffer CurrReadFb;
private FrameBuffer RawFb;
private bool FlipX;
private bool FlipY;
@ -67,111 +54,144 @@ namespace Ryujinx.Graphics.Gal.OpenGL
private int CropRight;
private int CropBottom;
public OGLFrameBuffer()
//This framebuffer is used to attach guest rendertargets,
//think of it as a dummy OpenGL VAO
private int DummyFrameBuffer;
//These framebuffers are used to blit images
private int SrcFb;
private int DstFb;
//Holds current attachments, used to avoid unnecesary calls to OpenGL
private int[] ColorAttachments;
private int DepthAttachment;
private int StencilAttachment;
public OGLFrameBuffer(OGLTexture Texture)
{
Fbs = new Dictionary<long, FrameBuffer>();
ColorAttachments = new int[8];
this.Texture = Texture;
}
public void Create(long Key, int Width, int Height)
public void BindColor(long Key, int Attachment)
{
if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
if (Fb.Width != Width ||
Fb.Height != Height)
{
SetupTexture(Fb.TexHandle, Width, Height);
EnsureFrameBuffer();
Fb.Width = Width;
Fb.Height = Height;
}
return;
Attach(ref ColorAttachments[Attachment], Tex.Handle, FramebufferAttachment.ColorAttachment0 + Attachment);
}
else
{
UnbindColor(Attachment);
}
Fb = new FrameBuffer(Width, Height, true);
SetupTexture(Fb.TexHandle, Width, Height);
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle);
GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fb.RbHandle);
GL.RenderbufferStorage(
RenderbufferTarget.Renderbuffer,
RenderbufferStorage.Depth24Stencil8,
Width,
Height);
GL.FramebufferRenderbuffer(
FramebufferTarget.Framebuffer,
FramebufferAttachment.DepthStencilAttachment,
RenderbufferTarget.Renderbuffer,
Fb.RbHandle);
GL.FramebufferTexture(
FramebufferTarget.Framebuffer,
FramebufferAttachment.ColorAttachment0,
Fb.TexHandle,
0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
Fbs.Add(Key, Fb);
}
public void Bind(long Key)
public void UnbindColor(int Attachment)
{
if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
{
GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle);
EnsureFrameBuffer();
CurrFb = Fb;
Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment);
}
public void BindZeta(long Key)
{
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
EnsureFrameBuffer();
if (Tex.HasDepth && Tex.HasStencil)
{
if (DepthAttachment != Tex.Handle ||
StencilAttachment != Tex.Handle)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthStencilAttachment,
Tex.Handle,
0);
DepthAttachment = Tex.Handle;
StencilAttachment = Tex.Handle;
}
}
else if (Tex.HasDepth)
{
Attach(ref DepthAttachment, Tex.Handle, FramebufferAttachment.DepthAttachment);
Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment);
}
else if (Tex.HasStencil)
{
Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment);
Attach(ref StencilAttachment, Tex.Handle, FramebufferAttachment.StencilAttachment);
}
else
{
throw new InvalidOperationException();
}
}
else
{
UnbindZeta();
}
}
public void UnbindZeta()
{
EnsureFrameBuffer();
if (DepthAttachment != 0 ||
StencilAttachment != 0)
{
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
FramebufferAttachment.DepthStencilAttachment,
0,
0);
DepthAttachment = 0;
StencilAttachment = 0;
}
}
public void BindTexture(long Key, int Index)
{
if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
GL.ActiveTexture(TextureUnit.Texture0 + Index);
GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle);
GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
}
}
public void Set(long Key)
{
if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
CurrReadFb = Fb;
ReadTex = Tex;
}
}
public void Set(byte[] Data, int Width, int Height)
{
if (RawFb == null)
if (RawTex == null)
{
CreateRawFb(Width, Height);
RawTex = new ImageHandler();
}
if (RawFb.Width != Width ||
RawFb.Height != Height)
{
SetupTexture(RawFb.TexHandle, Width, Height);
RawTex.EnsureSetup(new GalImage(Width, Height, RawFormat));
RawFb.Width = Width;
RawFb.Height = Height;
}
GL.BindTexture(TextureTarget.Texture2D, RawTex.Handle);
GL.ActiveTexture(TextureUnit.Texture0);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, RawTex.PixelFormat, RawTex.PixelType, Data);
GL.BindTexture(TextureTarget.Texture2D, RawFb.TexHandle);
(PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data);
CurrReadFb = RawFb;
ReadTex = RawTex;
}
public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom)
@ -208,60 +228,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void Render()
{
if (CurrReadFb != null)
if (ReadTex == null)
{
int SrcX0, SrcX1, SrcY0, SrcY1;
if (CropLeft == 0 && CropRight == 0)
{
SrcX0 = 0;
SrcX1 = CurrReadFb.Width;
}
else
{
SrcX0 = CropLeft;
SrcX1 = CropRight;
}
if (CropTop == 0 && CropBottom == 0)
{
SrcY0 = 0;
SrcY1 = CurrReadFb.Height;
}
else
{
SrcY0 = CropTop;
SrcY1 = CropBottom;
}
float RatioX = MathF.Min(1f, (Window.Height * (float)NativeWidth) / ((float)NativeHeight * Window.Width));
float RatioY = MathF.Min(1f, (Window.Width * (float)NativeHeight) / ((float)NativeWidth * Window.Height));
int DstWidth = (int)(Window.Width * RatioX);
int DstHeight = (int)(Window.Height * RatioY);
int DstPaddingX = (Window.Width - DstWidth) / 2;
int DstPaddingY = (Window.Height - DstHeight) / 2;
int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX;
int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX;
int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY;
int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY;
GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
GL.Viewport(0, 0, Window.Width, Window.Height);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrReadFb.Handle);
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear);
return;
}
int SrcX0, SrcX1, SrcY0, SrcY1;
if (CropLeft == 0 && CropRight == 0)
{
SrcX0 = 0;
SrcX1 = ReadTex.Width;
}
else
{
SrcX0 = CropLeft;
SrcX1 = CropRight;
}
if (CropTop == 0 && CropBottom == 0)
{
SrcY0 = 0;
SrcY1 = ReadTex.Height;
}
else
{
SrcY0 = CropTop;
SrcY1 = CropBottom;
}
float RatioX = MathF.Min(1f, (Window.Height * (float)NativeWidth) / ((float)NativeHeight * Window.Width));
float RatioY = MathF.Min(1f, (Window.Width * (float)NativeHeight) / ((float)NativeWidth * Window.Height));
int DstWidth = (int)(Window.Width * RatioX);
int DstHeight = (int)(Window.Height * RatioY);
int DstPaddingX = (Window.Width - DstWidth) / 2;
int DstPaddingY = (Window.Height - DstHeight) / 2;
int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX;
int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX;
int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY;
int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY;
if (SrcFb == 0) SrcFb = GL.GenFramebuffer();
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0);
GL.Viewport(0, 0, Window.Width, Window.Height);
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0);
GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear);
EnsureFrameBuffer();
}
public void Copy(
@ -276,39 +307,80 @@ namespace Ryujinx.Graphics.Gal.OpenGL
int DstX1,
int DstY1)
{
if (Fbs.TryGetValue(SrcKey, out FrameBuffer SrcFb) &&
Fbs.TryGetValue(DstKey, out FrameBuffer DstFb))
if (Texture.TryGetImage(SrcKey, out ImageHandler SrcTex) &&
Texture.TryGetImage(DstKey, out ImageHandler DstTex))
{
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb.Handle);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb.Handle);
if (SrcTex.HasColor != DstTex.HasColor ||
SrcTex.HasDepth != DstTex.HasDepth ||
SrcTex.HasStencil != DstTex.HasStencil)
{
throw new NotImplementedException();
}
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
ClearBufferMask.ColorBufferBit,
BlitFramebufferFilter.Linear);
if (SrcTex.HasColor)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.ColorAttachment0,
ClearBufferMask.ColorBufferBit,
true);
}
else if (SrcTex.HasDepth && SrcTex.HasStencil)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.DepthStencilAttachment,
ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit,
false);
}
else if (SrcTex.HasDepth)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.DepthAttachment,
ClearBufferMask.DepthBufferBit,
false);
}
else if (SrcTex.HasStencil)
{
CopyTextures(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
SrcTex.Handle,
DstTex.Handle,
FramebufferAttachment.StencilAttachment,
ClearBufferMask.StencilBufferBit,
false);
}
else
{
throw new InvalidOperationException();
}
}
}
}
public void GetBufferData(long Key, Action<byte[]> Callback)
{
if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, Fb.Handle);
byte[] Data = new byte[Tex.Width * Tex.Height * ImageHandler.MaxBpp];
byte[] Data = new byte[Fb.Width * Fb.Height * 4];
GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
(PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
GL.ReadPixels(
GL.GetTexImage(
TextureTarget.Texture2D,
0,
0,
Fb.Width,
Fb.Height,
Format,
Type,
Tex.PixelFormat,
Tex.PixelType,
Data);
Callback(Data);
@ -319,83 +391,101 @@ namespace Ryujinx.Graphics.Gal.OpenGL
long Key,
int Width,
int Height,
GalTextureFormat Format,
byte[] Buffer)
{
if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
if (Texture.TryGetImage(Key, out ImageHandler Tex))
{
GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle);
GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
const int Level = 0;
const int Border = 0;
const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba;
(PixelFormat GlFormat, PixelType Type) = OGLEnumConverter.GetTextureFormat(Format);
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Tex.InternalFormat,
Width,
Height,
Border,
GlFormat,
Type,
Tex.PixelFormat,
Tex.PixelType,
Buffer);
}
}
private void CreateRawFb(int Width, int Height)
private void EnsureFrameBuffer()
{
if (RawFb == null)
if (DummyFrameBuffer == 0)
{
RawFb = new FrameBuffer(Width, Height, false);
DummyFrameBuffer = GL.GenFramebuffer();
}
SetupTexture(RawFb.TexHandle, Width, Height);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
RawFb.Width = Width;
RawFb.Height = Height;
GL.BindFramebuffer(FramebufferTarget.Framebuffer, RawFb.Handle);
GL.DrawBuffers(8, DrawBuffers);
}
private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment)
{
if (OldHandle != NewHandle)
{
GL.FramebufferTexture(
FramebufferTarget.Framebuffer,
FramebufferAttachment.ColorAttachment0,
RawFb.TexHandle,
FramebufferTarget.DrawFramebuffer,
FbAttachment,
NewHandle,
0);
GL.Viewport(0, 0, Width, Height);
OldHandle = NewHandle;
}
}
private void SetupTexture(int Handle, int Width, int Height)
private void CopyTextures(
int SrcX0,
int SrcY0,
int SrcX1,
int SrcY1,
int DstX0,
int DstY0,
int DstX1,
int DstY1,
int SrcTexture,
int DstTexture,
FramebufferAttachment Attachment,
ClearBufferMask Mask,
bool Color)
{
GL.BindTexture(TextureTarget.Texture2D, Handle);
if (SrcFb == 0) SrcFb = GL.GenFramebuffer();
if (DstFb == 0) DstFb = GL.GenFramebuffer();
const int MinFilter = (int)TextureMinFilter.Linear;
const int MagFilter = (int)TextureMagFilter.Linear;
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter);
GL.FramebufferTexture(
FramebufferTarget.ReadFramebuffer,
Attachment,
SrcTexture,
0);
(PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
GL.FramebufferTexture(
FramebufferTarget.DrawFramebuffer,
Attachment,
DstTexture,
0);
const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba;
if (Color)
{
GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
}
const int Level = 0;
const int Border = 0;
GL.Clear(Mask);
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Width,
Height,
Border,
Format,
Type,
IntPtr.Zero);
GL.BlitFramebuffer(
SrcX0, SrcY0, SrcX1, SrcY1,
DstX0, DstY0, DstX1, DstY1,
Mask,
Color ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest);
EnsureFrameBuffer();
}
}
}

View file

@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
public class OGLRasterizer : IGalRasterizer
class OGLRasterizer : IGalRasterizer
{
private int[] VertexBuffers;
@ -44,36 +44,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void ClearBuffers(
GalClearBufferFlags Flags,
int Attachment,
float Red, float Green, float Blue, float Alpha,
float Depth,
int Stencil)
{
ClearBufferMask Mask = ClearBufferMask.ColorBufferBit;
GL.ColorMask(
Flags.HasFlag(GalClearBufferFlags.ColorRed),
Flags.HasFlag(GalClearBufferFlags.ColorGreen),
Flags.HasFlag(GalClearBufferFlags.ColorBlue),
Flags.HasFlag(GalClearBufferFlags.ColorAlpha));
GL.ClearBuffer(ClearBuffer.Color, Attachment, new float[] { Red, Green, Blue, Alpha });
if (Flags.HasFlag(GalClearBufferFlags.Depth))
{
Mask |= ClearBufferMask.DepthBufferBit;
GL.ClearBuffer(ClearBuffer.Depth, 0, ref Depth);
}
if (Flags.HasFlag(GalClearBufferFlags.Stencil))
{
Mask |= ClearBufferMask.StencilBufferBit;
GL.ClearBuffer(ClearBuffer.Stencil, 0, ref Stencil);
}
GL.ClearColor(Red, Green, Blue, Alpha);
GL.ClearDepth(Depth);
GL.ClearStencil(Stencil);
GL.Clear(Mask);
GL.ColorMask(true, true, true, true);
}

View file

@ -23,7 +23,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
Buffer = new OGLConstBuffer();
FrameBuffer = new OGLFrameBuffer();
Texture = new OGLTexture();
FrameBuffer = new OGLFrameBuffer(Texture as OGLTexture);
Rasterizer = new OGLRasterizer();
@ -31,8 +33,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
Pipeline = new OGLPipeline(Buffer as OGLConstBuffer, Rasterizer as OGLRasterizer, Shader as OGLShader);
Texture = new OGLTexture();
ActionsQueue = new ConcurrentQueue<Action>();
}

View file

@ -4,26 +4,13 @@ using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
public class OGLTexture : IGalTexture
class OGLTexture : IGalTexture
{
private class TCE
{
public int Handle;
public GalTexture Texture;
public TCE(int Handle, GalTexture Texture)
{
this.Handle = Handle;
this.Texture = Texture;
}
}
private OGLCachedResource<TCE> TextureCache;
private OGLCachedResource<ImageHandler> TextureCache;
public OGLTexture()
{
TextureCache = new OGLCachedResource<TCE>(DeleteTexture);
TextureCache = new OGLCachedResource<ImageHandler>(DeleteTexture);
}
public void LockCache()
@ -36,73 +23,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
TextureCache.Unlock();
}
private static void DeleteTexture(TCE CachedTexture)
private static void DeleteTexture(ImageHandler CachedImage)
{
GL.DeleteTexture(CachedTexture.Handle);
GL.DeleteTexture(CachedImage.Handle);
}
public void Create(long Key, byte[] Data, GalTexture Texture)
public void Create(long Key, byte[] Data, GalImage Image)
{
int Handle = GL.GenTexture();
TextureCache.AddOrUpdate(Key, new TCE(Handle, Texture), (uint)Data.Length);
TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length);
GL.BindTexture(TextureTarget.Texture2D, Handle);
const int Level = 0; //TODO: Support mipmap textures.
const int Border = 0;
if (IsCompressedTextureFormat(Texture.Format))
if (IsCompressedTextureFormat(Image.Format))
{
InternalFormat InternalFmt = OGLEnumConverter.GetCompressedTextureFormat(Texture.Format);
InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format);
GL.CompressedTexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Texture.Width,
Texture.Height,
Image.Width,
Image.Height,
Border,
Data.Length,
Data);
}
else
{
if (Texture.Format >= GalTextureFormat.Astc2D4x4)
if (Image.Format >= GalImageFormat.ASTC_BEGIN && Image.Format <= GalImageFormat.ASTC_END)
{
int TextureBlockWidth = GetAstcBlockWidth(Texture.Format);
int TextureBlockHeight = GetAstcBlockHeight(Texture.Format);
int TextureBlockWidth = GetAstcBlockWidth(Image.Format);
int TextureBlockHeight = GetAstcBlockHeight(Image.Format);
Data = ASTCDecoder.DecodeToRGBA8888(
Data,
TextureBlockWidth,
TextureBlockHeight, 1,
Texture.Width,
Texture.Height, 1);
Image.Width,
Image.Height, 1);
Texture.Format = GalTextureFormat.A8B8G8R8;
Image.Format = GalImageFormat.A8B8G8R8_UNORM_PACK32;
}
const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba;
(PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(Texture.Format);
(PixelInternalFormat InternalFormat, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format);
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Texture.Width,
Texture.Height,
InternalFormat,
Image.Width,
Image.Height,
Border,
Format,
Type,
Data);
}
int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Texture.XSource);
int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Texture.YSource);
int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Texture.ZSource);
int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Texture.WSource);
int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Image.XSource);
int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Image.YSource);
int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource);
int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Image.WSource);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, SwizzleR);
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, SwizzleG);
@ -110,76 +95,100 @@ namespace Ryujinx.Graphics.Gal.OpenGL
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, SwizzleA);
}
private static int GetAstcBlockWidth(GalTextureFormat Format)
public void CreateFb(long Key, long Size, GalImage Image)
{
if (!TryGetImage(Key, out ImageHandler CachedImage))
{
CachedImage = new ImageHandler();
TextureCache.AddOrUpdate(Key, CachedImage, Size);
}
CachedImage.EnsureSetup(Image);
}
public bool TryGetImage(long Key, out ImageHandler CachedImage)
{
if (TextureCache.TryGetValue(Key, out CachedImage))
{
return true;
}
CachedImage = null;
return false;
}
private static int GetAstcBlockWidth(GalImageFormat Format)
{
switch (Format)
{
case GalTextureFormat.Astc2D4x4: return 4;
case GalTextureFormat.Astc2D5x5: return 5;
case GalTextureFormat.Astc2D6x6: return 6;
case GalTextureFormat.Astc2D8x8: return 8;
case GalTextureFormat.Astc2D10x10: return 10;
case GalTextureFormat.Astc2D12x12: return 12;
case GalTextureFormat.Astc2D5x4: return 5;
case GalTextureFormat.Astc2D6x5: return 6;
case GalTextureFormat.Astc2D8x6: return 8;
case GalTextureFormat.Astc2D10x8: return 10;
case GalTextureFormat.Astc2D12x10: return 12;
case GalTextureFormat.Astc2D8x5: return 8;
case GalTextureFormat.Astc2D10x5: return 10;
case GalTextureFormat.Astc2D10x6: return 10;
case GalImageFormat.ASTC_4x4_UNORM_BLOCK: return 4;
case GalImageFormat.ASTC_5x5_UNORM_BLOCK: return 5;
case GalImageFormat.ASTC_6x6_UNORM_BLOCK: return 6;
case GalImageFormat.ASTC_8x8_UNORM_BLOCK: return 8;
case GalImageFormat.ASTC_10x10_UNORM_BLOCK: return 10;
case GalImageFormat.ASTC_12x12_UNORM_BLOCK: return 12;
case GalImageFormat.ASTC_5x4_UNORM_BLOCK: return 5;
case GalImageFormat.ASTC_6x5_UNORM_BLOCK: return 6;
case GalImageFormat.ASTC_8x6_UNORM_BLOCK: return 8;
case GalImageFormat.ASTC_10x8_UNORM_BLOCK: return 10;
case GalImageFormat.ASTC_12x10_UNORM_BLOCK: return 12;
case GalImageFormat.ASTC_8x5_UNORM_BLOCK: return 8;
case GalImageFormat.ASTC_10x5_UNORM_BLOCK: return 10;
case GalImageFormat.ASTC_10x6_UNORM_BLOCK: return 10;
}
throw new ArgumentException(nameof(Format));
}
private static int GetAstcBlockHeight(GalTextureFormat Format)
private static int GetAstcBlockHeight(GalImageFormat Format)
{
switch (Format)
{
case GalTextureFormat.Astc2D4x4: return 4;
case GalTextureFormat.Astc2D5x5: return 5;
case GalTextureFormat.Astc2D6x6: return 6;
case GalTextureFormat.Astc2D8x8: return 8;
case GalTextureFormat.Astc2D10x10: return 10;
case GalTextureFormat.Astc2D12x12: return 12;
case GalTextureFormat.Astc2D5x4: return 4;
case GalTextureFormat.Astc2D6x5: return 5;
case GalTextureFormat.Astc2D8x6: return 6;
case GalTextureFormat.Astc2D10x8: return 8;
case GalTextureFormat.Astc2D12x10: return 10;
case GalTextureFormat.Astc2D8x5: return 5;
case GalTextureFormat.Astc2D10x5: return 5;
case GalTextureFormat.Astc2D10x6: return 6;
case GalImageFormat.ASTC_4x4_UNORM_BLOCK: return 4;
case GalImageFormat.ASTC_5x5_UNORM_BLOCK: return 5;
case GalImageFormat.ASTC_6x6_UNORM_BLOCK: return 6;
case GalImageFormat.ASTC_8x8_UNORM_BLOCK: return 8;
case GalImageFormat.ASTC_10x10_UNORM_BLOCK: return 10;
case GalImageFormat.ASTC_12x12_UNORM_BLOCK: return 12;
case GalImageFormat.ASTC_5x4_UNORM_BLOCK: return 4;
case GalImageFormat.ASTC_6x5_UNORM_BLOCK: return 5;
case GalImageFormat.ASTC_8x6_UNORM_BLOCK: return 6;
case GalImageFormat.ASTC_10x8_UNORM_BLOCK: return 8;
case GalImageFormat.ASTC_12x10_UNORM_BLOCK: return 10;
case GalImageFormat.ASTC_8x5_UNORM_BLOCK: return 5;
case GalImageFormat.ASTC_10x5_UNORM_BLOCK: return 5;
case GalImageFormat.ASTC_10x6_UNORM_BLOCK: return 6;
}
throw new ArgumentException(nameof(Format));
}
public bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture)
public bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image)
{
if (TextureCache.TryGetSize(Key, out long Size) && Size == DataSize)
{
if (TextureCache.TryGetValue(Key, out TCE CachedTexture))
if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
{
Texture = CachedTexture.Texture;
Image = CachedImage.Image;
return true;
}
}
Texture = default(GalTexture);
Image = default(GalImage);
return false;
}
public void Bind(long Key, int Index)
{
if (TextureCache.TryGetValue(Key, out TCE CachedTexture))
if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
{
GL.ActiveTexture(TextureUnit.Texture0 + Index);
GL.BindTexture(TextureTarget.Texture2D, CachedTexture.Handle);
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
}
}
@ -208,18 +217,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color);
}
private static bool IsCompressedTextureFormat(GalTextureFormat Format)
private static bool IsCompressedTextureFormat(GalImageFormat Format)
{
switch (Format)
{
case GalTextureFormat.BC6H_UF16:
case GalTextureFormat.BC6H_SF16:
case GalTextureFormat.BC7U:
case GalTextureFormat.BC1:
case GalTextureFormat.BC2:
case GalTextureFormat.BC3:
case GalTextureFormat.BC4:
case GalTextureFormat.BC5:
case GalImageFormat.BC6H_UFLOAT_BLOCK:
case GalImageFormat.BC6H_SFLOAT_BLOCK:
case GalImageFormat.BC7_UNORM_BLOCK:
case GalImageFormat.BC1_RGBA_UNORM_BLOCK:
case GalImageFormat.BC2_UNORM_BLOCK:
case GalImageFormat.BC3_UNORM_BLOCK:
case GalImageFormat.BC4_SNORM_BLOCK:
case GalImageFormat.BC4_UNORM_BLOCK:
case GalImageFormat.BC5_SNORM_BLOCK:
case GalImageFormat.BC5_UNORM_BLOCK:
return true;
}