[VideoOut] Add Bink2 support via FFMPEG bridge (#527)

* [VideoOut] Add Bink2 support via FFMPEG bridge

* [CMake] update commit

* [CMake] update commit
This commit is contained in:
Berk
2026-07-22 21:41:41 +03:00
committed by GitHub
parent d3600c9255
commit f704586a8d
22 changed files with 3087 additions and 310 deletions
+157 -50
View File
@@ -1,6 +1,9 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
namespace SharpEmu.Libs.Agc;
/// <summary>
@@ -16,8 +19,11 @@ namespace SharpEmu.Libs.Agc;
/// other D/R and pipe/bank-XOR modes stay opt-in while their complete AddrLib
/// equations are being ported.
/// </summary>
internal static class GnmTiling
internal static unsafe class GnmTiling
{
private const int ParallelDetileElementThreshold = 512 * 512;
private const int MaxDetileWorkers = 4;
// Oberon uses the 16-pipe / 8-pixel-packer RB+ topology. These are the
// single-sample 64 KiB equations generated by AMD AddrLib for that exact
// topology. Each entry describes one address bit as an XOR of X/Y bits.
@@ -118,6 +124,14 @@ internal static class GnmTiling
StringComparison.Ordinal);
private static readonly HashSet<uint> _reportedModes = new();
private static readonly ConcurrentDictionary<(uint SwizzleMode, int BppLog2), PatternTerms>
_patternTermCache = new();
private static readonly ConcurrentDictionary<(SwizzleKind Kind, int Width, int Height), int[]>
_blockTableCache = new();
private static readonly ParallelOptions _parallelDetileOptions = new()
{
MaxDegreeOfParallelism = Math.Min(MaxDetileWorkers, Environment.ProcessorCount),
};
public static bool Enabled => _enabled || !_disabled;
@@ -404,64 +418,83 @@ internal static class GnmTiling
return false;
}
// Precompute the within-block element offset for each (x, y) inside a
// single block. The swizzle equation only depends on the in-block
// coordinates, so this table is reused for every block — turning the
// per-pixel bit-interleave (a loop + calls) into a single array lookup.
// Detiling a 2048x2048 texture is millions of elements; without this the
// per-pixel math makes DETILE unusably slow during asset streaming.
// Address tables depend only on the swizzle equation and element size,
// so retain them across textures instead of rebuilding them per upload.
var hasExactXorPattern = TryGetExactXorPattern(swizzleMode, bppLog2, out var xorPattern);
var blockTable = hasExactXorPattern ? [] : new int[blockWidth * blockHeight];
for (var by = 0; !hasExactXorPattern && by < blockHeight; by++)
{
for (var bx = 0; bx < blockWidth; bx++)
{
blockTable[by * blockWidth + bx] = (int)(kind == SwizzleKind.ZOrder
? MortonInterleave((uint)bx, (uint)by, blockWidth, blockHeight)
: StandardSwizzleOffset((uint)bx, (uint)by, blockWidth, blockHeight));
}
}
var patternTerms = hasExactXorPattern
? _patternTermCache.GetOrAdd(
(swizzleMode, bppLog2),
_ => CreatePatternTerms(xorPattern))
: default;
var blockTable = hasExactXorPattern
? []
: _blockTableCache.GetOrAdd(
(kind, blockWidth, blockHeight),
static key => CreateBlockTable(key.Kind, key.Width, key.Height));
// The XOR equation offset factors cleanly into independent X and Y
// fields — each output bit is parity(x & XMask) XOR parity(y & YMask),
// and parity distributes over XOR, so offset(x, y) == xTerm(x) ^ yTerm(y).
// Precompute the per-column X term once (reused across every row) so the
// inner loop drops from a 16-bit interleave with 32 PopCounts per element
// to a single array load and one XOR.
var xTermByColumn = hasExactXorPattern ? new int[elementsWide] : [];
for (var x = 0; hasExactXorPattern && x < elementsWide; x++)
// Exact equations repeat at a small power-of-two period. Cached axis
// terms reduce the inner loop to two array loads and one XOR.
fixed (byte* tiledPointer = tiled)
fixed (byte* linearPointer = linear)
{
xTermByColumn[x] = (int)PatternAxisTerm((uint)x, xorPattern, useX: true);
}
for (var y = 0; y < elementsHigh; y++)
{
var blockY = y / blockHeight;
var inBlockY = y % blockHeight;
var rowBlockBase = (long)blockY * blocksPerRow;
var tableRowBase = inBlockY * blockWidth;
var destRowBase = (long)y * elementsWide * bytesPerElement;
// Y term is constant across the row; hoist it out of the inner loop.
var yTerm = hasExactXorPattern ? (int)PatternAxisTerm((uint)y, xorPattern, useX: false) : 0;
for (var x = 0; x < elementsWide; x++)
var sourceAddress = (nint)tiledPointer;
var destinationAddress = (nint)linearPointer;
var sourceLength = tiled.Length;
var destinationLength = linear.Length;
var blockWidthShift = BitLog2((uint)blockWidth);
var blockWidthMask = blockWidth - 1;
var detileRow = (int y) =>
{
var blockX = x / blockWidth;
var inBlockX = x % blockWidth;
var blockIndex = rowBlockBase + blockX;
var sourceByte = hasExactXorPattern
? blockIndex * blockBytes + (xTermByColumn[x] ^ yTerm)
: (blockIndex * blockElements + blockTable[tableRowBase + inBlockX]) *
(long)bytesPerElement;
var destByte = destRowBase + (long)x * bytesPerElement;
if (sourceByte + bytesPerElement > tiled.Length ||
destByte + bytesPerElement > linear.Length)
var blockY = y / blockHeight;
var inBlockY = y & (blockHeight - 1);
var rowBlockBase = (long)blockY * blocksPerRow;
var tableRowBase = inBlockY * blockWidth;
var destRowBase = (long)y * elementsWide * bytesPerElement;
var yTerm = hasExactXorPattern
? patternTerms.Y[y & patternTerms.YMask]
: 0;
for (var x = 0; x < elementsWide; x++)
{
continue;
}
var blockX = x >> blockWidthShift;
var inBlockX = x & blockWidthMask;
var blockIndex = rowBlockBase + blockX;
var sourceByte = hasExactXorPattern
? blockIndex * blockBytes + (patternTerms.X[x & patternTerms.XMask] ^ yTerm)
: (blockIndex * blockElements + blockTable[tableRowBase + inBlockX]) *
(long)bytesPerElement;
var destByte = destRowBase + (long)x * bytesPerElement;
if (sourceByte < 0 ||
sourceByte + bytesPerElement > sourceLength ||
destByte + bytesPerElement > destinationLength)
{
continue;
}
tiled.Slice((int)sourceByte, bytesPerElement)
.CopyTo(linear.Slice((int)destByte, bytesPerElement));
CopyElement(
(byte*)sourceAddress + sourceByte,
(byte*)destinationAddress + destByte,
bytesPerElement);
}
};
var elementCount = (long)elementsWide * elementsHigh;
if (elementCount >= ParallelDetileElementThreshold && Environment.ProcessorCount > 1)
{
Parallel.For(
0,
elementsHigh,
_parallelDetileOptions,
detileRow);
}
else
{
for (var y = 0; y < elementsHigh; y++)
{
detileRow(y);
}
}
}
@@ -474,6 +507,80 @@ internal static class GnmTiling
ZOrder,
}
private readonly record struct PatternTerms(int[] X, int XMask, int[] Y, int YMask);
private static PatternTerms CreatePatternTerms(AddressBit[] pattern)
{
uint xMask = 0;
uint yMask = 0;
foreach (var bit in pattern)
{
xMask |= bit.XMask;
yMask |= bit.YMask;
}
var xLength = AxisTermPeriod(xMask);
var yLength = AxisTermPeriod(yMask);
var xTerms = new int[xLength];
var yTerms = new int[yLength];
for (var x = 0; x < xTerms.Length; x++)
{
xTerms[x] = (int)PatternAxisTerm((uint)x, pattern, useX: true);
}
for (var y = 0; y < yTerms.Length; y++)
{
yTerms[y] = (int)PatternAxisTerm((uint)y, pattern, useX: false);
}
return new PatternTerms(xTerms, xLength - 1, yTerms, yLength - 1);
}
private static int AxisTermPeriod(uint mask) =>
mask == 0 ? 1 : 1 << (32 - System.Numerics.BitOperations.LeadingZeroCount(mask));
private static int[] CreateBlockTable(SwizzleKind kind, int blockWidth, int blockHeight)
{
var table = new int[blockWidth * blockHeight];
for (var y = 0; y < blockHeight; y++)
{
for (var x = 0; x < blockWidth; x++)
{
table[y * blockWidth + x] = (int)(kind == SwizzleKind.ZOrder
? MortonInterleave((uint)x, (uint)y, blockWidth, blockHeight)
: StandardSwizzleOffset((uint)x, (uint)y, blockWidth, blockHeight));
}
}
return table;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CopyElement(byte* source, byte* destination, int bytesPerElement)
{
switch (bytesPerElement)
{
case 1:
*destination = *source;
break;
case 2:
Unsafe.WriteUnaligned(destination, Unsafe.ReadUnaligned<ushort>(source));
break;
case 4:
Unsafe.WriteUnaligned(destination, Unsafe.ReadUnaligned<uint>(source));
break;
case 8:
Unsafe.WriteUnaligned(destination, Unsafe.ReadUnaligned<ulong>(source));
break;
case 16:
Unsafe.WriteUnaligned(destination, Unsafe.ReadUnaligned<UInt128>(source));
break;
default:
Unsafe.CopyBlockUnaligned(destination, source, (uint)bytesPerElement);
break;
}
}
private static readonly AddressBit Zero = new(0, 0);
private static AddressBit X(int bit) => new(1u << bit, 0);