using System.Collections.Generic; using UdonSharp; using UnityEngine; using VRC.SDK3.Rendering; using VRC.Udon.Common.Interfaces; public class DataDecoder : UdonSharpBehaviour { public RenderTexture sourceTexture; public int tileToCheck = 0; private int tileSize = 8; // Minimum size (in pixels) of a tile. This is shared with our tixl operator. private const int kMinTileSize = 4; private const int kMaxTileSize = 128; private Color32[] pixelData; private bool hasData = false; private int readWidth; private int readHeight; void Start() {} void Update() { if (sourceTexture == null) return; int requestWidth = Mathf.Min(tileSize, sourceTexture.width); int requestHeight = sourceTexture.height; int pixelCount = requestWidth * requestHeight; if (pixelCount <= 0) return; if (pixelData == null || pixelCount != pixelData.Length) { pixelData = new Color32[pixelCount]; hasData = false; } readWidth = requestWidth; readHeight = requestHeight; VRCAsyncGPUReadback.Request(sourceTexture, 0, 0, readWidth, 0, readHeight, 0, 1, (IUdonEventReceiver)this); if (hasData) { ProcessTiles(); hasData = false; } } public override void OnAsyncGpuReadbackComplete(VRCAsyncGPUReadbackRequest request) { if (request.hasError) return; if (pixelData != null && request.TryGetData(pixelData)) { hasData = true; } } private void ProcessTiles() { // Get the tile size. { int oldTileSize = tileSize; tileSize = kMinTileSize; tileSize = Parse24BitTile(0); tileSize = Mathf.Clamp(tileSize, kMinTileSize, kMaxTileSize); if (tileSize != oldTileSize) { Debug.Log($"Tile size changed from {oldTileSize} to {tileSize}"); } } // Get the length. This is in units of subpixels. So we will need to access // ceil(length/3) tiles. int lengthSubpixels = Parse24BitTile(1); int lengthTiles = (int) Mathf.Ceil(lengthSubpixels/3.0f); // Collect all nibbles into a flat array. Note that these are still // encoded. var nibbles = new List(lengthSubpixels); for (int tile_i = 0; tile_i < lengthTiles; tile_i++) { GetTileRGB(tile_i+2, out int r, out int g, out int b); nibbles.Add(r); if (nibbles.Count < nibbles.Capacity) { nibbles.Add(g); } if (nibbles.Count < nibbles.Capacity) { nibbles.Add(b); } } // Convert nibbles to bytes. var bytes = nibbles; for (int i = 0; i < nibbles.Count/2; i++) { // See DataEncoder.cs. It puts the upper 4 bits before the lower 4 bits. bytes[i] = (nibbles[2*i] & 0xF0) | ((nibbles[2*i+1] & 0xF0) >> 4); } // Remove second half of list. bytes.RemoveRange(nibbles.Count/2, nibbles.Count/2); int tilesPerColumn = (int) Mathf.Floor(readHeight / tileSize); } private int Parse24BitTile(int tileIdx) { GetTileRGB(tileIdx, out int r, out int g, out int b); int data = 0; data |= DecodeNibble(r); data |= DecodeNibble(g) << 4; data |= DecodeNibble(b) << 8; return data; } private int DecodeNibble(int subpixel) { return (subpixel >> 4) & 0x0F; } private void GetTileRGB(int tileIdx, out int r, out int g, out int b) { r = 0; g = 0; b = 0; int tileY = tileIdx * tileSize; int centerY = tileY + tileSize / 2; if (centerY >= readHeight) return; int localX = readWidth / 2; int localY = readHeight - 1 - centerY; int index = localY * readWidth + localX; if (index < 0 || index >= pixelData.Length) return; Color32 c = pixelData[index]; r = c.r; g = c.g; b = c.b; } }