//#define COSMOSDEBUG using System; using System.Collections.Generic; using System.Drawing; using Cosmos.HAL.Drivers; namespace Cosmos.System.Graphics { /// /// VBECanvas class. Used to control screen, by VBE (VESA BIOS Extensions) standard. See also: . /// public class VBECanvas : Canvas { /// /// Default video mode. 1024x768x32. /// private static readonly Mode _DefaultMode = new Mode(1024, 768, ColorDepth.ColorDepth32); /// /// Driver for Setting vbe modes and ploting/getting pixels /// private readonly VBEDriver _VBEDriver; /// /// Video mode. /// private Mode _Mode; /// /// Create new instance of the class. /// /// Thrown if default mode (1024x768x32) is not suppoted. public VBECanvas() : this(_DefaultMode) { } /// /// Create new instance of the class. /// /// VBE mode. /// Thrown if mode is not suppoted. public VBECanvas(Mode aMode) { Global.mDebugger.SendInternal($"Creating new VBEScreen() with mode {aMode.Columns}x{aMode.Rows}x{(uint)aMode.ColorDepth}"); if (Core.VBE.IsAvailable()) { Core.VBE.ModeInfo ModeInfo = Core.VBE.getModeInfo(); aMode = new Mode(ModeInfo.width, ModeInfo.height, (ColorDepth)ModeInfo.bpp); Global.mDebugger.SendInternal($"Detected VBE VESA with {aMode.Columns}x{aMode.Rows}x{(uint)aMode.ColorDepth}"); } ThrowIfModeIsNotValid(aMode); _VBEDriver = new VBEDriver((ushort)aMode.Columns, (ushort)aMode.Rows, (ushort)aMode.ColorDepth); Mode = aMode; } /// /// Disables VBE Graphics mode, parent method returns to VGA text mode (80x25) /// public override void Disable() { _VBEDriver.DisableDisplay(); } /// /// Get and set video mode. /// /// (set) Thrown if mode is not suppoted. public override Mode Mode { get => _Mode; set { _Mode = value; SetMode(_Mode); } } #region Display /// /// All the available screen modes VBE supports, I would like to query the hardware and obtain from it the list but I have /// not yet find how to do it! For now I hardcode the most used VESA modes, VBE seems to support until HDTV resolution /// without problems that is well... excellent :-) /// //public override IReadOnlyList AvailableModes { get; } = new List /// /// Available VBE supported video modes. /// /// Low res: /// /// 320x240x32. /// 640x480x32. /// 800x600x32. /// 1024x768x32. /// /// /// /// HD: /// /// 1280x720x32. /// 1280x1024x32. /// /// /// /// HDR: /// /// 1366x768x32. /// 1680x1050x32. /// /// /// /// HDTV: /// /// 1920x1080x32. /// 1920x1200x32. /// /// /// public override List AvailableModes { get; } = new List { new Mode(320, 240, ColorDepth.ColorDepth32), new Mode(640, 480, ColorDepth.ColorDepth32), new Mode(800, 600, ColorDepth.ColorDepth32), new Mode(1024, 768, ColorDepth.ColorDepth32), /* The so called HD-Ready resolution */ new Mode(1280, 720, ColorDepth.ColorDepth32), new Mode(1280, 768, ColorDepth.ColorDepth32), new Mode(1280, 1024, ColorDepth.ColorDepth32), /* A lot of HD-Ready screen uses this instead of 1280x720 */ new Mode(1366, 768, ColorDepth.ColorDepth32), new Mode(1680, 1050, ColorDepth.ColorDepth32), /* HDTV resolution */ new Mode(1920, 1080, ColorDepth.ColorDepth32), /* HDTV resolution (16:10 AR) */ new Mode(1920, 1200, ColorDepth.ColorDepth32), }; /// /// Override Canvas default graphics mode. /// public override Mode DefaultGraphicMode => _DefaultMode; /// /// Use this to setup the screen, this will disable the console. /// /// The desired Mode resolution /// Thrown if mode is not suppoted. private void SetMode(Mode aMode) { ThrowIfModeIsNotValid(aMode); ushort xres = (ushort)Mode.Columns; ushort yres = (ushort)Mode.Rows; ushort bpp = (ushort)Mode.ColorDepth; //set the screen _VBEDriver.VBESet(xres, yres, bpp); } #endregion #region Drawing /// /// Clear screen to specified color. /// /// Color. public override void Clear(Color aColor) { Global.mDebugger.SendInternal($"Clearing the Screen with Color {aColor}"); //if (color == null) // throw new ArgumentNullException(nameof(color)); /* * TODO this version of Clear() works only when mode.ColorDepth == ColorDepth.ColorDepth32 * in the other cases you should before convert color and then call the opportune ClearVRAM() overload * (the one that takes ushort for ColorDepth.ColorDepth16 and the one that takes byte for ColorDepth.ColorDepth8) * For ColorDepth.ColorDepth24 you should mask the Alpha byte. */ switch (_Mode.ColorDepth) { case ColorDepth.ColorDepth4: throw new NotImplementedException(); case ColorDepth.ColorDepth8: throw new NotImplementedException(); case ColorDepth.ColorDepth16: throw new NotImplementedException(); case ColorDepth.ColorDepth24: throw new NotImplementedException(); case ColorDepth.ColorDepth32: _VBEDriver.ClearVRAM((uint)aColor.ToArgb()); break; default: throw new NotImplementedException(); } } /* * As DrawPoint() is the basic block of DrawLine() and DrawRect() and in theory of all the future other methods that will * be implemented is better to not check the validity of the arguments here or it will repeat the check for any point * to be drawn slowing down all. */ /// /// Draw point to the screen. /// /// Pen to draw the point with. /// X coordinate. /// Y coordinate. /// Thrown if color depth is not supported (currently only 32 is supported). public override void DrawPoint(Pen aPen, int aX, int aY) { Color color = aPen.Color; uint offset; /* * For now we can Draw only if the ColorDepth is 32 bit, we will throw otherwise. * * How to support other ColorDepth? The offset calculation should be the same (and so could be done out of the switch) * ColorDepth.ColorDepth16 and ColorDepth.ColorDepth8 need a conversion from color (an ARGB32 color) to the RGB16 and RGB8 * how to do this conversion faster maybe using pre-computed tables? What happens if the color cannot be converted? We will throw? */ switch (Mode.ColorDepth) { case ColorDepth.ColorDepth32: offset = (uint)GetPointOffset(aX, aY); Global.mDebugger.SendInternal($"Drawing Point of color {color} at offset {offset}"); _VBEDriver.SetVRAM(offset, color.B); _VBEDriver.SetVRAM(offset + 1, color.G); _VBEDriver.SetVRAM(offset + 2, color.R); _VBEDriver.SetVRAM(offset + 3, color.A); Global.mDebugger.SendInternal("Point drawn"); break; case ColorDepth.ColorDepth24: offset = (uint)GetPointOffset(aX, aY); Global.mDebugger.SendInternal($"Drawing Point of color {color} at offset {offset}"); _VBEDriver.SetVRAM(offset, color.B); _VBEDriver.SetVRAM(offset + 1, color.G); _VBEDriver.SetVRAM(offset + 2, color.R); Global.mDebugger.SendInternal("Point drawn"); break; default: string errorMsg = "DrawPoint() with ColorDepth " + (int)Mode.ColorDepth + " not yet supported"; throw new NotImplementedException(errorMsg); } } /// /// Draw point to the screen. /// Not implemented. /// /// Pen to draw the point with. /// X coordinate. /// Y coordinate. /// Thrown always (only int coordinats supported). public override void DrawPoint(Pen aPen, float aX, float aY) { throw new NotImplementedException(); } /* This is just temp */ /// /// Draw array of colors. /// /// Colors array. /// X coordinate. /// Y coordinate. /// Width. /// unused. /// Thrown if coordinates are invalid, or width is less than 0. /// Thrown if color depth is not supported. public override void DrawArray(Color[] aColors, int aX, int aY, int aWidth, int aHeight) { ThrowIfCoordNotValid(aX, aY); ThrowIfCoordNotValid(aX + aWidth, aY + aHeight); for (int i = 0; i < aX; i++) { for (int ii = 0; ii < aY; ii++) { DrawPoint(new Pen(aColors[i + (ii * aWidth)]), i, ii); } } } /// /// Get point offset. /// /// X coordinate. /// Y coordinate. /// int value. private int GetPointOffset(int aX, int aY) { Global.mDebugger.SendInternal($"Computing offset for coordinates {aX},{aY}"); int xBytePerPixel = (int)Mode.ColorDepth / 8; int stride = (int)Mode.ColorDepth / 8; int pitch = Mode.Columns * xBytePerPixel; return (aX * stride) + (aY * pitch); } /// /// Draw filled rectangle. /// /// Pen to draw with. /// X coordinate. /// Y coordinate. /// Width. /// Height. public override void DrawFilledRectangle(Pen aPen, int aX, int aY, int aWidth, int aHeight) { int xOffset = GetPointOffset(aX, aY); int xScreenWidthInPixel = Mode.Columns * ((int)Mode.ColorDepth / 8); aWidth *= (int)Mode.ColorDepth / 8; for (int i = 0; i < aHeight; i++) { _VBEDriver.ClearVRAM((i * xScreenWidthInPixel) + xOffset, aWidth, aPen.Color.ToArgb()); } } /// /// Draw image. /// /// Image. /// X coordinate. /// Y coordinate. public override void DrawImage(Image aImage, int aX, int aY) { var xBitmap = aImage.rawData; var xWidht = (int)aImage.Width; var xHeight = (int)aImage.Height; int xOffset = GetPointOffset(aX, aY); int xScreenWidthInPixel = Mode.Columns * ((int)Mode.ColorDepth / 8); Global.mDebugger.SendInternal($"Drawing image of size {aImage.Width}x{aImage.Height} array size {aImage.rawData.Length}"); for (int i = 0; i < xHeight; i++) { _VBEDriver.CopyVRAM((i * xScreenWidthInPixel) + xOffset, xBitmap, (i * xWidht), xWidht); } Global.mDebugger.SendInternal("Done"); } #endregion public override void Display() { _VBEDriver.Swap(); } #region Reading /// /// Get point color. /// /// X coordinate. /// Y coordinate. /// Color value. public override Color GetPointColor(int aX, int aY) { uint offset = (uint)GetPointOffset(aX, aY); return Color.FromArgb((int)_VBEDriver.GetVRAM(offset)); } #endregion } }