Skip to content

Instantly share code, notes, and snippets.

@jessefreeman
Created August 29, 2018 13:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jessefreeman/3d52ceecc1d3eaa7d76a19dc3e53d28e to your computer and use it in GitHub Desktop.
Save jessefreeman/3d52ceecc1d3eaa7d76a19dc3e53d28e to your computer and use it in GitHub Desktop.
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using PixelVisionRunner;
using PixelVisionSDK;
using PixelVisionSDK.Utils;
namespace MonoGameRunner.Data
{
public class DisplayTarget : IDisplayTarget
{
private GraphicsDeviceManager graphicManager;
private Texture2D renderTexture;
private SpriteBatch spriteBatch;
private int _totalPixels;
private int totalPixels
{
get { return _totalPixels; }
set
{
if (cachedPixels.Length != value)
{
// Now it's time to resize our cahcedPixels array. We calculate the total number of pixels by multiplying the width by the
// height. We'll use this array to make sure we have enough pixels to correctly render the DisplayChip's own pixel data.
Array.Resize(ref cachedPixels, value);
}
_totalPixels = value;
}
}
private int bgColorID;
private int[] pixelData;
private int colorRef;
private IColor[] colorsData;
private int i;
private IColor colorData;
public Color[] cachedColors = new Color[0];
protected Color[] cachedPixels = new Color[0];
protected int totalCachedColors;
private Rectangle visibleRect;
private readonly GraphicsDeviceManager _graphicsDeviceManager;
private int _monitorWidth = 640;
private int _monitorHeight = 640;
// TODO think we just need to pass in the active game and not the entire runner?
public DisplayTarget(GraphicsDeviceManager graphicManager, int width, int height, int scale = 1, bool fullscreen = false)
{
this.graphicManager = graphicManager;
this.graphicManager.HardwareModeSwitch = false;
spriteBatch = new SpriteBatch(graphicManager.GraphicsDevice);
_monitorWidth = width.Clamp(64, 640);
_monitorHeight = height.Clamp(64, 480);
monitorScale = scale;
}
private int _monitorScale = 1;
public int monitorScale
{
get { return _monitorScale; }
set { _monitorScale = value.Clamp(1, 6); }
}
public Vector2 scale = new Vector2(1, 1);
public Vector2 offset;
private GameWindow window;
public void ResetResolution(IEngine activeEngine, bool fullscreen, bool matchResolution, bool stretch)
{
var displayChip = activeEngine.displayChip;
var gameWidth = displayChip.width;
var gameHeight = displayChip.height;
var overScanX = displayChip.overscanXPixels;
var overScanY = displayChip.overscanYPixels;
renderTexture?.Dispose();
renderTexture = new Texture2D(graphicManager.GraphicsDevice, gameWidth, gameHeight);
// Calculate the game's resolution
visibleRect.Width = renderTexture.Width - overScanX;
visibleRect.Height = renderTexture.Height - overScanY;
var tmpMonitorScale = fullscreen ? 1 : monitorScale;
// Calculate the monitor's resolution
var displayWidth = fullscreen ? GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width : (_monitorWidth *
tmpMonitorScale);
var displayHeight = fullscreen ? GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height : _monitorHeight * tmpMonitorScale;
// Calculate the game scale
// TODO need to figure out scale
scale.X = (float)displayWidth / visibleRect.Width;
scale.Y = (float)displayHeight/ visibleRect.Height;
if (!stretch)
{
// To preserve the aspect ratio,
// use the smaller scale factor.
scale.X = Math.Min(scale.X, scale.Y);
scale.Y = scale.X;
}
offset.X = (displayWidth - (visibleRect.Width * scale.X)) * .5f;
offset.Y = (displayHeight - (visibleRect.Height * scale.Y)) * .5f;
if (matchResolution && !fullscreen)
{
displayWidth = Math.Min(displayWidth, (int) (visibleRect.Width * scale.X));
displayHeight = Math.Min(displayHeight, (int) (visibleRect.Height * scale.Y));
offset.X = 0;
offset.Y = 0;
}
Console.WriteLine("Reset Res Fullscreen " + fullscreen + " "+displayWidth+"x"+displayHeight);
// Apply changes
graphicManager.IsFullScreen = fullscreen;
graphicManager.PreferredBackBufferWidth = displayWidth;
graphicManager.PreferredBackBufferHeight = displayHeight;
graphicManager.ApplyChanges();
// Update the controller to use the correct mouse scale
activeEngine.controllerChip.MouseScale(scale.X, scale.Y);
// Set the new number of pixels
totalPixels = displayChip.totalPixels;
}
public void Render(IEngine activeEngine)
{
if (activeEngine == null) return;
// The first part of rendering Pixel Vision 8's DisplayChip is to get all of the current pixel data during the current frame. Each
// Integer in this Array contains an ID we can use to match up to the cached colors we created when setting up the Runner.
pixelData = activeEngine.displayChip.pixels; //.displayPixelData;
// Need to make sure we are using the latest colors.
if (activeEngine.colorChip.invalid)
CacheColors(activeEngine);
// Now it's time to loop through all of the DisplayChip's pixel data.
for (i = 0; i < totalPixels; i++)
{
// Here we get a reference to the color we are trying to look up from the pixelData array. Then we compare that ID to what we
// have in the cachedPixels. If the color is out of range, we use the cachedTransparentColor. If the color exists in the cache we use that.
colorRef = pixelData[i];
// Replace transparent colors with bg for next pass
if (colorRef < 0 || (colorRef >= totalCachedColors))
{
colorRef = bgColorID;
}
cachedPixels[i] = cachedColors[colorRef];
// As you can see, we are using a protected field called cachedPixels. When we call ResetResolution, we resize this array to make sure that
// it matches the length of the DisplayChip's pixel data. By keeping a reference to this Array and updating each color instead of rebuilding
// it, we can significantly increase the render performance of the Runner.
}
renderTexture.SetData(cachedPixels);
spriteBatch.Begin(samplerState: SamplerState.PointClamp);
spriteBatch.Draw(renderTexture, offset, visibleRect, Color.White, 0f, Vector2.Zero, scale, SpriteEffects.None, 1f);
spriteBatch.End();
}
/// <summary>
/// To optimize the Runner, we need to save a reference to each color in the ColorChip as native Unity Colors. The
/// cached
/// colors will improve rendering performance later when we cover the DisplayChip's pixel data into a format the
/// Texture2D
/// can display.
/// </summary>
public void CacheColors(IEngine activeEngine)
{
// We also want to cache the ScreenBufferChip's background color. The background color is an ID that references one of the ColorChip's colors.
bgColorID = activeEngine.colorChip.backgroundColor;
// The ColorChip can return an array of ColorData. ColorData is an internal data structure that Pixel Vision 8 uses to store
// color information. It has properties for a Hex representation as well as RGB.
colorsData = activeEngine.colorChip.colors;
// To improve performance, we'll save a reference to the total cashed colors directly to the Runner's totalCachedColors field.
// Also, we'll create a new array to store native Unity Color classes.
totalCachedColors = colorsData.Length;
if (cachedColors.Length != totalCachedColors)
Array.Resize(ref cachedColors, totalCachedColors);
// Now it's time to loop through each of the colors and convert them from ColorData to Color instances.
for (i = 0; i < totalCachedColors; i++)
{
// Converting ColorData to Unity Colors is relatively straight forward by simply passing the ColorData's RGB properties into
// the Unity Color class's constructor and saving it to the cachedColors array.
colorData = colorsData[i];
// TODO is there a better way to do this without having to update all 256 colors?
cachedColors[i] = new Color(colorData.r, colorData.g, colorData.b);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment