1 //
2 // Copyright (c) 2010-2023 Antmicro
3 // Copyright (c) 2018-2022 Microchip
4 //
5 // This file is licensed under the MIT License.
6 // Full license text is available in 'licenses/MIT.txt'.
7 //
8 // Basic generic graphical controller without any 2D/3D acceleration.
9 // Example baremetal drivers located at https://github.com/AntonKrug/mustein_gpu_driver
10 //
11 // v0.2 2018/11/29 anton.krug@microchip.com SanFrancisco Summit variant
12 // v0.3 2019/02/13 anton.krug@microchip.com More color modes, embedded memory and added versality
13 //
14 
15 using Antmicro.Renode.Backends.Display;
16 using Antmicro.Renode.Core;
17 using Antmicro.Renode.Core.Structure.Registers;
18 using Antmicro.Renode.Logging;
19 using Antmicro.Renode.Peripherals.Bus;
20 using Antmicro.Renode.Peripherals.Memory;
21 using System;
22 using System.Collections.Generic;
23 
24 namespace Antmicro.Renode.Peripherals.Video
25 {
26     [AllowedTranslations(AllowedTranslation.ByteToDoubleWord)]
27     public class MusteinGenericGPU : AutoRepaintingVideo, IDoubleWordPeripheral, IKnownSize
28     {
MusteinGenericGPU(IMachine machine, MappedMemory buffer, bool registers64bitAligned = false, int controlBit = 23, uint frameBufferSize = 0x800000)29         public MusteinGenericGPU(IMachine machine, MappedMemory buffer, bool registers64bitAligned = false, int controlBit = 23, uint frameBufferSize = 0x800000) : base(machine)
30         {
31             this.machine = machine;
32             this.frameBufferSize = frameBufferSize;
33             this.controlBit = controlBit;
34             this.controlOffset = 1U << controlBit;
35             this.is64bitAligned = registers64bitAligned;
36             this.accessAligment = (registers64bitAligned) ? 8 : 4;
37             this.sync = new object();
38             this.underlyingBuffer = buffer;
39 
40             // Allows to switch ordering for the 8bit mode lookup table depending what is closer to the native host colorspace
41             var lookupTableRgbx = false;
42 #if !PLATFORM_WINDOWS
43             lookupTableRgbx = true;
44 #endif
45 
46             // Populating lookup table for the 8bit color mode to 24bit conversion, because the 332 format is unbalanced so
47             // much and the Red/Green have 50% more bits than Blue the value 0xFF has a yellow tint. Balanced white (gray)
48             // color is actually value 0xF6. The white color is fairly gray because the discarted least significant bits
49             // still acumulate to a 1/4 of the total brightness/value (we are cutting away too many bits).
50             colorTable = new uint[256];
51             for(var index = 0u; index < colorTable.Length; index++)
52             {
53                 var red = index & 0x7;
54                 var green = (index & 0x38) >> 3;
55                 var blue = (index & 0xc0) >> 6;
56                 var value = 0u;
57                 if(lookupTableRgbx)
58                 {
59                     value = red << 21 | green << 13 | blue << 6;  // Converting RGB332 to RGB888 which will be used for RGBX8888
60                 }
61                 else
62                 {
63                     value = red << 5  | green << 13 | blue << 22; // Converting RGB332 to BGR888 which will be used for BGRX8888
64                 }
65                 colorTable[index] = value;
66             }
67 
68             colorModeToPixelFormatTable = new Dictionary<ColorMode, PixelFormat>()
69             {
70                 { ColorMode.LowColor,  (lookupTableRgbx) ? PixelFormat.RGBX8888 : PixelFormat.BGRX8888 },
71                 { ColorMode.HighColor, PixelFormat.RGB565 },
72                 { ColorMode.TrueColor, PixelFormat.RGBX8888 }
73             };
74 
75             Reconfigure(DefaultWidth, DefaultHeight, DefaultColor);
76 
77             copyPatterns = new Dictionary<Tuple<ColorMode, bool, PixelPacking>, Action>()
78             {
79                 { Tuple.Create(ColorMode.LowColor,  false, PixelPacking.SinglePixelPerWrite), () => ConvertAndSkip(1, 3) },
80                 { Tuple.Create(ColorMode.HighColor, false, PixelPacking.SinglePixelPerWrite), () => CopyAndSkip(2, 2) },
81                 { Tuple.Create(ColorMode.TrueColor, false, PixelPacking.SinglePixelPerWrite), CopyFully },
82 
83                 { Tuple.Create(ColorMode.LowColor,  false, PixelPacking.FullyPacked32bit), ConvertFully },
84                 { Tuple.Create(ColorMode.HighColor, false, PixelPacking.FullyPacked32bit), CopyFully },
85                 { Tuple.Create(ColorMode.TrueColor, false, PixelPacking.FullyPacked32bit), CopyFully },
86 
87                 // In a 32bit peripheral aligment mode using fully packed 64bit is ilegal and will act as fully packed 32bit
88                 { Tuple.Create(ColorMode.LowColor,  false, PixelPacking.FullyPacked64bit), ConvertFully },
89                 { Tuple.Create(ColorMode.HighColor, false, PixelPacking.FullyPacked64bit), CopyFully },
90                 { Tuple.Create(ColorMode.TrueColor, false, PixelPacking.FullyPacked64bit), CopyFully },
91 
92                 { Tuple.Create(ColorMode.LowColor,  true,  PixelPacking.SinglePixelPerWrite), () => ConvertAndSkip(1, 7) },
93                 { Tuple.Create(ColorMode.HighColor, true,  PixelPacking.SinglePixelPerWrite), () => CopyAndSkip(2, 6) },
94                 { Tuple.Create(ColorMode.TrueColor, true,  PixelPacking.SinglePixelPerWrite), () => CopyAndSkip(4, 4) },
95 
96                 { Tuple.Create(ColorMode.LowColor,  true, PixelPacking.FullyPacked32bit), () => ConvertAndSkip(4, 4) },
97                 { Tuple.Create(ColorMode.HighColor, true, PixelPacking.FullyPacked32bit), () => CopyAndSkip(2, 4) },
98                 { Tuple.Create(ColorMode.TrueColor, true, PixelPacking.FullyPacked32bit), () => CopyAndSkip(4, 4) },
99 
100                 { Tuple.Create(ColorMode.LowColor,  true, PixelPacking.FullyPacked64bit), ConvertFully },
101                 { Tuple.Create(ColorMode.HighColor, true, PixelPacking.FullyPacked64bit), CopyFully },
102                 { Tuple.Create(ColorMode.TrueColor, true, PixelPacking.FullyPacked64bit), CopyFully },
103             };
104 
105             // Populate the control registers addresses
106             GenerateRegisterCollection();
107         }
108 
WriteDoubleWord(long address, uint value)109         public void WriteDoubleWord(long address, uint value)
110         {
111             registers.Write(address, value);
112         }
113 
ReadDoubleWord(long offset)114         public uint ReadDoubleWord(long offset)
115         {
116             return registers.Read(offset);
117         }
118 
Reset()119         public override void Reset()
120         {
121             registers.Reset();
122             colorMode = default(ColorMode);
123             pixelPacking = default(PixelPacking);
124         }
125 
ChangePacking(PixelPacking packing)126         public void ChangePacking(PixelPacking packing)
127         {
128             this.Log(LogLevel.Noisy, "The display is using {0} packing format", packing);
129             pixelPacking = packing;
130         }
131 
132         public long Size => controlOffset * 2; // Peripheral is split into 2 equal partitions (buffer and control registers)
133 
Repaint()134         protected override void Repaint()
135         {
136             if(copyPatterns.TryGetValue(Tuple.Create(colorMode, is64bitAligned, pixelPacking), out Action command))
137             {
138                 lock(sync)
139                 {
140                     command.Invoke();
141                 }
142             }
143             else
144             {
145                 this.Log(LogLevel.Error, "Unsuported colorMode ({0}, {1}, {2}), aligment and pixel packing is used", colorMode, is64bitAligned, pixelPacking);
146             }
147         }
148 
Reconfigure(uint? setWidth = null, uint? setHeight = null, ColorMode? setColor = null)149         private void Reconfigure(uint? setWidth = null, uint? setHeight = null, ColorMode? setColor = null)
150         {
151             var finalFormat = Format;
152             if(setColor != null)
153             {
154                 if(colorModeToPixelFormatTable.TryGetValue((ColorMode)setColor, out finalFormat))
155                 {
156                     colorMode = (ColorMode)setColor;
157                 }
158                 else
159                 {
160                     this.Log(LogLevel.Error, "Setting wrong color value {0}, keeping original value {1}", setColor, finalFormat);
161                 }
162             }
163 
164             lock(sync)
165             {
166                 base.Reconfigure((int?)setWidth, (int?)setHeight, finalFormat);
167             }
168 
169             this.Log(LogLevel.Noisy, "The display is reconfigured to {0}x{1} with {2} color format (setColor ={3})", Width, Height, Format.ToString(), setColor);
170 
171             if(((setWidth != null) || (setHeight != null)) && ((Width * Height * accessAligment) > frameBufferSize))
172             {
173                 this.Log(LogLevel.Warning, "This resolution with some (or all) pixel packing modes will not fit in the frameBuffer, if needed increase the frameBufferSize.");
174             }
175         }
176 
GenerateRegisterCollection()177         private void GenerateRegisterCollection()
178         {
179             var registerDictionary = new Dictionary<long, DoubleWordRegister>
180             {
181                 { controlOffset + (long)Registers.Width * accessAligment, new DoubleWordRegister(this, DefaultWidth)
182                     .WithValueField(0, 16, name: "Width", writeCallback: (_, x) => Reconfigure(setWidth: (uint)x), valueProviderCallback: _ => (uint)Width)
183                 },
184 
185                 { controlOffset + (long)Registers.Height * accessAligment, new DoubleWordRegister(this, DefaultHeight)
186                     .WithValueField(0, 16, name: "Height", writeCallback: (_, y) => Reconfigure(setHeight: (uint)y), valueProviderCallback: _ => (uint)Height)
187                 },
188 
189                 { controlOffset + (long)Registers.Format * accessAligment, new DoubleWordRegister(this, (uint)DefaultColor | ((uint)DefaultPacking << 4))
190                     .WithEnumField<DoubleWordRegister, ColorMode>(0, 4, name: "Color", writeCallback: (_, c) => Reconfigure(setColor: c), valueProviderCallback: _ => colorMode)
191                     .WithEnumField<DoubleWordRegister, PixelPacking>(4, 4, name: "Packing", writeCallback: (_, c) => ChangePacking(c), valueProviderCallback: _ => pixelPacking)
192                 }
193             };
194 
195             if(is64bitAligned)
196             {
197                 // Dormant registers for the high 32bit accesses of the 64bit registers,
198                 // just so there will not be logged any unimplemented accesses
199                 foreach(long registerIndex in Enum.GetValues(typeof(Registers)))
200                 {
201                     registerDictionary.Add(
202                         controlOffset + registerIndex * accessAligment + 4, new DoubleWordRegister(this, 0x0)
203                             .WithTag("Dormant upper 32bit of 64bit registers", 0, 32)
204                     );
205                 }
206             }
207             registers = new DoubleWordRegisterCollection(this, registerDictionary);
208         }
209 
210         // We can convert from the 332 format the whole lot of bytes without skipping bytes in the input buffer
ConvertFully()211         private void ConvertFully()
212         {
213             var colorValues = underlyingBuffer.ReadBytes(0, buffer.Length * 4);
214             for(var i = 0; i < buffer.Length; ++i)
215             {
216                 buffer[i] = 0;
217                 buffer[i] = colorValues[i * 4 + 1];
218                 buffer[i] = (byte)(colorValues[i * 4 + 2] >> 8);
219                 buffer[i] = (byte)(colorValues[i * 4 + 3] >> 16);
220             }
221         }
222 
223         // When there are parts to skip and the the rest needs to be converted from 332 format
ConvertAndSkip(int bytesToCopy, int bytesToSkip)224         private void ConvertAndSkip(int bytesToCopy, int bytesToSkip)
225         {
226             var indexDest = 0;
227             var indexSrc = 0;
228             while(indexDest < buffer.Length)
229             {
230                 for(var indexPack = 0u; indexPack < bytesToCopy; indexPack++, indexSrc++)
231                 {
232                     HandleByte(indexSrc, ref indexDest);
233                 }
234                 indexSrc += bytesToSkip;
235             }
236         }
237 
HandleByte(int indexSrc, ref int indexDest)238         private void HandleByte(int indexSrc, ref int indexDest)
239         {
240             // Each byte gets transfered via the lookup table (to transfer the 8bit 332 format to a format the backend can display
241             var colorValue = colorTable[underlyingBuffer.ReadDoubleWord(indexSrc)];
242             buffer[indexDest++] = 0;
243             buffer[indexDest++] = (byte)(colorValue);
244             buffer[indexDest++] = (byte)(colorValue >> 8);
245             buffer[indexDest++] = (byte)(colorValue >> 16);
246         }
247 
248         // We can copy the whole lot as it is. No conversion between the bytes
CopyFully()249         private void CopyFully()
250         {
251             var bytes = underlyingBuffer.ReadBytes(0, buffer.Length);
252             for(var index = 0; index < buffer.Length; index++)
253             {
254                 buffer[index] = bytes[index];
255             }
256         }
257 
258         // When there are parts to skip but no conversion needed
CopyAndSkip(int bytesToCopy, int bytesToSkip)259         private void CopyAndSkip(int bytesToCopy, int bytesToSkip)
260         {
261             var indexDest = 0;
262             var indexSrc  = 0;
263             while(indexDest < buffer.Length)
264             {
265                 for(var indexPack = 0; indexPack < bytesToCopy; indexPack++, indexDest++, indexSrc++)
266                 {
267                     buffer[indexDest] = underlyingBuffer.ReadByte(indexSrc);
268                 }
269                 indexSrc += bytesToSkip;
270             }
271         }
272 
273         private DoubleWordRegisterCollection registers;
274         private object sync;
275         private ColorMode colorMode;
276         private PixelPacking pixelPacking;
277         private readonly MappedMemory underlyingBuffer;
278 
279         private readonly IMachine machine;
280         private readonly int accessAligment;
281         private readonly bool is64bitAligned;
282         private readonly int controlBit;
283         private readonly uint controlOffset;
284         private readonly uint frameBufferSize;
285         private readonly uint[] colorTable;
286         private readonly Dictionary<Tuple<ColorMode, bool, PixelPacking>, Action> copyPatterns;
287         private readonly Dictionary<ColorMode, PixelFormat>  colorModeToPixelFormatTable;
288 
289         private const int Alignment = 0x1000;
290         private const uint DefaultWidth = 128;
291         private const uint DefaultHeight = 128;
292         private const ColorMode DefaultColor = ColorMode.TrueColor;
293         private const PixelPacking DefaultPacking = PixelPacking.SinglePixelPerWrite;
294 
295         public enum PixelPacking : uint
296         {
297             SinglePixelPerWrite = 0,
298             FullyPacked32bit = 1,
299             FullyPacked64bit = 2
300         }
301 
302         private enum Registers : long
303         {
304             Width = 0x0,
305             Height = 0x1,
306             Format = 0x2
307         }
308 
309         private enum ColorMode : uint
310         {
311             LowColor = 0,   // 8-bit  per pixel, 3-bits Red, 3-bits Green and 2-bits Blue
312             HighColor = 1,  // 16-bit per pixel, 5-bits Red, 6-bits Green and 5-bits Blue
313             TrueColor = 2   // 32-bit per pixel, 8-bits Red, 8-bits Green and 8-bits Blue
314         }
315     }
316 }
317