commit 0b2510826bdc430cf804b18b4a412adeb477fed8
Author: spaceflint <>
Date: Sun Nov 15 11:14:42 2020 +0200
Initial commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13d7896
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.obj/
+.vs/
+packages/
+/TODO
diff --git a/BNA/BNA.csproj b/BNA/BNA.csproj
new file mode 100644
index 0000000..f1935ed
--- /dev/null
+++ b/BNA/BNA.csproj
@@ -0,0 +1,58 @@
+
+
+
+ {53E4C9F9-CE12-4067-8336-663D3582DCBA}
+ Library
+ BNA
+ BNA
+ OnOutputUpdated
+ true
+
+ None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ %22:@(FilterItem, '%22 %22:')%22
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/BNA/FNA.filter b/BNA/FNA.filter
new file mode 100644
index 0000000..35438da
--- /dev/null
+++ b/BNA/FNA.filter
@@ -0,0 +1,104 @@
+
+*.Bounding*
+*.Color
+*.ContainmentType
+*.DisplayOrientation
+
+*.DrawableGameComponent
+*.Game
+*.GameComponent
+*.GameComponentCollection
+*.GameComponentCollectionEventArgs
+*.GameServiceContainer
+*.GameTime
+*.GameWindow
+
+*.GraphicsDeviceInformation
+*.GraphicsDeviceManager
+
+*.IDrawable
+*.IGameComponent
+*.IGraphicsDeviceManager
+*.IUpdateable
+
+*.MathHelper
+*.Matrix
+
+*.PlayerIndex
+*.Point
+*.PreparingDeviceSettingsEventArgs
+
+*.Quaternion
+
+*.Rectangle
+
+*.Vector2
+*.Vector3
+*.Vector4
+
+*.Input.ButtonState
+*.Input.MouseState
+*.Input.Touch.GestureDetector
+*.Input.Touch.GestureSample
+*.Input.Touch.GestureType
+*.Input.Touch.TouchLocation
+*.Input.Touch.TouchLocationState
+*.Input.Touch.TouchPanel
+*.Input.Touch.TouchPanelCapabilities
+
+*.Graphics.BasicEffect
+*.Graphics.Blend
+*.Graphics.BlendFunction
+*.Graphics.BlendState
+*.Graphics.BufferUsage
+*.Graphics.ClearOptions
+*.Graphics.ColorWriteChannels
+*.Graphics.CompareFunction
+*.Graphics.CubeMapFace
+*.Graphics.CullMode
+*.Graphics.DepthFormat
+*.Graphics.DepthStencilState
+*.Graphics.DirectionalLight
+*.Graphics.DisplayMode
+*.Graphics.DisplayModeCollection
+*.Graphics.DxtUtil
+*.Graphics.DynamicVertexBuffer
+*.Graphics.EffectDirtyFlags
+*.Graphics.EffectHelpers
+*.Graphics.EffectParameterCollection
+*.Graphics.EffectPass
+*.Graphics.EffectPassCollection
+*.Graphics.EffectTechnique
+*.Graphics.EffectTechniqueCollection
+*.Graphics.FillMode
+*.Graphics.GraphicsAdapter
+*.Graphics.GraphicsDevice
+*.Graphics.GraphicsProfile
+*.Graphics.GraphicsResource
+*.Graphics.IGraphicsDeviceService
+*.Graphics.IEffect*
+*.Graphics.IndexBuffer
+*.Graphics.IndexElementSize
+*.Graphics.IRenderTarget
+*.Graphics.IVertexType
+*.Graphics.NoSuitableGraphicsDeviceException
+*.Graphics.PipelineCache
+*.Graphics.PresentationParameters
+*.Graphics.PresentInterval
+*.Graphics.PrimitiveType
+*.Graphics.RasterizerState
+*.Graphics.RenderTarget*
+*.Graphics.SamplerState
+*.Graphics.SamplerStateCollection
+*.Graphics.Sprite*
+*.Graphics.StateHash
+*.Graphics.StencilOperation
+*.Graphics.SurfaceFormat
+*.Graphics.Texture*
+*.Graphics.Vertex*
+*.Graphics.Viewport
+
+*.Graphics.PackedVector.*
+
+*.Content.*
+*.Storage.*
diff --git a/BNA/src/Activity.cs b/BNA/src/Activity.cs
new file mode 100644
index 0000000..675c910
--- /dev/null
+++ b/BNA/src/Activity.cs
@@ -0,0 +1,119 @@
+
+namespace Microsoft.Xna.Framework
+{
+
+ public class Activity : android.app.Activity
+ {
+
+ //
+ // Android onCreate
+ //
+
+ protected override void onCreate(android.os.Bundle savedInstanceState)
+ {
+ base.onCreate(savedInstanceState);
+
+ _LogTag = GetMetaAttr("log.tag") ?? _LogTag;
+
+ new java.lang.Thread(gameRunner = new GameRunner(this)).start();
+ }
+
+ //
+ // FinishAndRestart
+ //
+
+ public void FinishAndRestart(bool restart)
+ {
+ if (! (isFinishing() || isChangingConfigurations()))
+ {
+ runOnUiThread(((java.lang.Runnable.Delegate) (() =>
+ {
+ finish();
+ restartActivity = restart;
+ })).AsInterface());
+ }
+ }
+
+ //
+ // Android events forwarded to GameRunner:
+ // onPause, onResume, onDestroy, onTouchEvent
+ //
+
+ protected override void onPause()
+ {
+ gameRunner?.ActivityPause();
+ base.onPause();
+ }
+
+ protected override void onResume()
+ {
+ gameRunner?.ActivityResume();
+ base.onResume();
+ }
+
+ protected override void onDestroy()
+ {
+ gameRunner?.ActivityDestroy();
+ base.onDestroy();
+
+ if (restartActivity)
+ {
+ // note, do not use activity.recreate() here, as it occasionally
+ // keeps the old surface locked for a few seconds
+ startActivity(getIntent()
+ .addFlags(android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ .addFlags(android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME)
+ .addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)
+ );
+ }
+
+ // we have to destroy the activity process to get rid of leaking
+ // static references that can never be garbage collected
+
+ java.lang.System.exit(0);
+ }
+
+ public override bool onTouchEvent(android.view.MotionEvent motionEvent)
+ {
+ gameRunner?.ActivityTouch(motionEvent);
+ return true;
+ }
+
+ //
+ // GetMetaAttr
+ //
+
+ public string GetMetaAttr(string name, bool warn = false)
+ {
+ var info = getPackageManager().getActivityInfo(getComponentName(),
+ android.content.pm.PackageManager.GET_ACTIVITIES
+ | android.content.pm.PackageManager.GET_META_DATA);
+ name = "microsoft.xna.framework." + name;
+ var str = info?.metaData?.getString(name);
+ if (string.IsNullOrEmpty(str))
+ {
+ if (warn)
+ Activity.Log($"missing metadata attribute '{name}'");
+ str = null;
+ }
+ return str;
+ }
+
+ //
+ // Log
+ //
+
+ public static void Log(string s) => android.util.Log.i(_LogTag, s);
+
+ private static string _LogTag = "BNA_Game";
+
+ //
+ // data
+ //
+
+ private GameRunner gameRunner;
+ private bool restartActivity;
+
+ }
+
+}
diff --git a/BNA/src/Effect.cs b/BNA/src/Effect.cs
new file mode 100644
index 0000000..d661568
--- /dev/null
+++ b/BNA/src/Effect.cs
@@ -0,0 +1,881 @@
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using android.opengl;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+ public class Effect : GraphicsResource
+ {
+
+ //
+ // Effect
+ //
+
+ public Effect(GraphicsDevice graphicsDevice, byte[] effectCode)
+ {
+ GraphicsDevice = graphicsDevice;
+ CreateProgram(System.Text.Encoding.ASCII.GetString(effectCode));
+ CollectUniforms();
+ }
+
+
+
+ //
+ // CreateProgram
+ //
+
+ void CreateProgram(string text)
+ {
+ var (vertOfs, vertHdr) = HeaderOffset(text, "--- vertex ---", 0);
+ var (fragOfs, fragHdr) = HeaderOffset(text, "--- fragment ---", vertOfs);
+ var (stopOfs, stopHdr) = HeaderOffset(text, "--- end ---", fragOfs);
+
+ string versionDef = "#version 300 es\n";
+ string precisionDef = "#ifdef GL_FRAGMENT_PRECISION_HIGH \n"
+ + "precision highp float; \n"
+ + "#else \n"
+ + "precision mediump float; \n"
+ + "#endif \n";
+
+ string vertText = versionDef + text.Substring(vertOfs + vertHdr,
+ fragOfs - (vertOfs + vertHdr));
+ string fragText = versionDef + precisionDef
+ + text.Substring(fragOfs + fragHdr,
+ stopOfs - (fragOfs + fragHdr));
+
+ var err = CreateProgram2(vertText, fragText);
+ if (err != null)
+ throw new System.InvalidProgramException("Effect: " + err);
+
+ SetTechnique(text.Substring(0, vertOfs));
+
+ (int, int) HeaderOffset(string text, string header, int index)
+ {
+ index = text.IndexOf(header, index);
+ if (index == -1)
+ throw new System.InvalidProgramException("Effect: " + header);
+ return (index, header.Length);
+ }
+ }
+
+ //
+ // CreateProgram2
+ //
+
+ public string CreateProgram2(string vertexText, string fragmentText)
+ {
+ string errText = null;
+ Renderer.Get(GraphicsDevice.GLDevice).Send( () =>
+ {
+ (vertexId, errText) = CompileShader(
+ GLES20.GL_VERTEX_SHADER, "vertex", vertexText);
+ if (errText == null)
+ {
+ (fragmentId, errText) = CompileShader(
+ GLES20.GL_FRAGMENT_SHADER, "fragment", fragmentText);
+ if (errText == null)
+ {
+ (programId, errText) = LinkProgram(vertexId, fragmentId);
+ if (errText == null)
+ {
+ return; // success
+ }
+ }
+ GLES20.glDeleteShader(fragmentId);
+ }
+ GLES20.glDeleteShader(vertexId);
+ });
+ return errText;
+
+ // CompileShader
+
+ (int, string) CompileShader(int kind, string errKind, string text)
+ {
+ //GameRunner.Log($"SHADER PROGRAM: [[[" + text + "]]]");
+ string errText = null;
+ int shaderId = GLES20.glCreateShader(kind);
+ int errCode = GLES20.glGetError();
+ if (shaderId == 0 || errCode != 0)
+ errText = "glCreateShader";
+ else
+ {
+ GLES20.glShaderSource(shaderId, text);
+ errCode = GLES20.glGetError();
+ if (errCode != 0)
+ errText = "glShaderSource";
+ else
+ {
+ GLES20.glCompileShader(shaderId);
+ errCode = GLES20.glGetError();
+ if (errCode != 0)
+ errText = "glCompileShader";
+ else
+ {
+ var status = new int[1];
+ GLES20.glGetShaderiv(
+ shaderId, GLES20.GL_COMPILE_STATUS, status, 0);
+ errCode = GLES20.glGetError();
+ if (errCode == 0 && status[0] != 0)
+ {
+ return (shaderId, null); // success
+ }
+ errText = "compile error: "
+ + GLES20.glGetShaderInfoLog(shaderId);
+ }
+ GLES20.glDeleteShader(shaderId);
+ }
+ }
+ if (errCode != 0)
+ errText = "GL error " + errCode + ": " + errText;
+ errText = "in " + errKind + " shader: " + errText;
+ return (0, errText);
+ }
+
+ // LinkProgram
+
+ (int, string) LinkProgram(int vertexId, int fragmentId)
+ {
+ string errText = null;
+ int programId = GLES20.glCreateProgram();
+ int errCode = GLES20.glGetError();
+ if (programId == 0 || errCode != 0)
+ errText = "glCreateProgram";
+ else
+ {
+ GLES20.glAttachShader(programId, vertexId);
+ errCode = GLES20.glGetError();
+ if (errCode != 0)
+ errText = "glAttachShader (vertex)";
+ else
+ {
+ GLES20.glAttachShader(programId, fragmentId);
+ errCode = GLES20.glGetError();
+ if (errCode != 0)
+ errText = "glAttachShader (fragment)";
+ else
+ {
+ GLES20.glLinkProgram(programId);
+ errCode = GLES20.glGetError();
+ if (errCode != 0)
+ errText = "glLinkProgram";
+ else
+ {
+ var status = new int[1];
+ GLES20.glGetProgramiv(
+ programId, GLES20.GL_LINK_STATUS, status, 0);
+ errCode = GLES20.glGetError();
+ if (errCode == 0 && status[0] != 0)
+ {
+ return (programId, null); // success
+ }
+ errText = "link error: "
+ + GLES20.glGetProgramInfoLog(programId);
+ }
+ GLES20.glDetachShader(programId, fragmentId);
+ }
+ GLES20.glDetachShader(programId, vertexId);
+ }
+ GLES20.glDeleteProgram(programId);
+ }
+ if (errCode != 0)
+ errText = "GL error " + errCode + ": " + errText;
+ errText = "in shader program: " + errText;
+ return (0, errText);
+ }
+ }
+
+ //
+ // SetTechnique
+ //
+
+ void SetTechnique(string text)
+ {
+ string name;
+ string search = "#technique ";
+ int idx = text.IndexOf(search);
+ if (idx != -1)
+ name = text.Substring(idx + search.Length).Trim();
+ else
+ name = "Default";
+
+ var passes = new List();
+ passes.Add(new EffectPass(name, null, this, IntPtr.Zero, 0));
+ var list = new List();
+ technique = new EffectTechnique(name, IntPtr.Zero,
+ new EffectPassCollection(passes), null);
+ list.Add(technique);
+ Techniques = new EffectTechniqueCollection(list);
+ }
+
+
+
+ //
+ // CollectUniforms
+ //
+
+ void CollectUniforms()
+ {
+ var list = new List();
+ var uniforms = GetProgramUniforms();
+ if (uniforms != null)
+ {
+ for (int i = 0; i < uniforms.Length; i++)
+ {
+ var (name, type, size) = uniforms[i];
+ list.Add(new EffectParameter(name, type, size));
+ }
+ }
+ Parameters = new EffectParameterCollection(list);
+ }
+
+ //
+ // GetProgramUniforms
+ //
+
+ public (string name, int type, int size)[] GetProgramUniforms()
+ {
+ (string, int, int)[] result = null;
+ Renderer.Get(GraphicsDevice.GLDevice).Send( () =>
+ {
+ var count = new int[1];
+ GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, count, 0);
+ byte[] nameBuf = new byte[count[0] + 1];
+ GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, count, 0);
+ if (count[0] == 0)
+ return;
+
+ result = new (string name, int type, int size)[count[0]];
+ var type = new int[1];
+ var size = new int[1];
+ for (int i = 0; i < result.Length; i++)
+ {
+ GLES20.glGetActiveUniform(programId, i, nameBuf.Length,
+ /* length, lengthOffset */ count, 0,
+ /* size, sizeOffset */ size, 0,
+ /* type, typeOffset */ type, 0,
+ /* name, nameOffset */ (sbyte[]) (object) nameBuf, 0);
+ var nameStr = System.Text.Encoding.ASCII.GetString(nameBuf, 0, count[0]);
+ result[i] = (nameStr, type[0], size[0]);
+ }
+ });
+ return result;
+ }
+
+
+
+ //
+ // INTERNAL_applyEffect
+ //
+
+ public void INTERNAL_applyEffect(uint pass)
+ {
+ var graphicsDevice = GraphicsDevice;
+ Renderer.Get(graphicsDevice.GLDevice).Send( () =>
+ {
+ GLES20.glUseProgram(programId);
+ int n = Parameters.Count;
+ for (int i = 0; i < n; i++)
+ {
+ if (! Parameters[i].Apply(i, graphicsDevice))
+ {
+ throw new ArgumentException(
+ $"uniform {Parameters[i].Name} (#{i}) in effect {technique.Name}");
+ }
+ }
+ });
+ }
+
+
+
+ //
+ // Dispose
+ //
+
+ protected override void Dispose(bool disposing)
+ {
+ if (! base.IsDisposed)
+ {
+ Renderer.Get(GraphicsDevice.GLDevice).Send( () =>
+ {
+ GLES20.glDeleteShader(fragmentId);
+ GLES20.glDeleteShader(vertexId);
+ GLES20.glDeleteProgram(programId);
+ fragmentId = 0;
+ vertexId = 0;
+ programId = 0;
+ });
+ }
+ }
+
+
+
+ //
+ // data
+ //
+
+ private int programId;
+ private int vertexId, fragmentId;
+
+ public EffectParameterCollection Parameters { get; private set; }
+ public EffectTechniqueCollection Techniques { get; private set; }
+
+ private EffectTechnique technique;
+ public EffectTechnique CurrentTechnique
+ {
+ get => technique;
+ set => throw new System.PlatformNotSupportedException();
+ }
+
+ protected internal virtual void OnApply() { }
+ }
+
+
+
+ public sealed class EffectParameter
+ {
+ public unsafe EffectParameter(string name, int type, int size)
+ {
+ Name = name;
+ this.type = type;
+
+ int floatCount = 0;
+ int intCount = 0;
+
+ if (type == android.opengl.GLES20.GL_FLOAT)
+ {
+ floatCount = 1;
+ if (size == 1 && name == "MultiplierY")
+ kind = 'Y';
+ }
+
+ else if (type == android.opengl.GLES20.GL_FLOAT_VEC2)
+ floatCount = 2;
+ else if (type == android.opengl.GLES20.GL_FLOAT_VEC3)
+ floatCount = 3;
+ else if (type == android.opengl.GLES20.GL_FLOAT_VEC4)
+ floatCount = 4;
+
+ else if (type == android.opengl.GLES20.GL_FLOAT_MAT4)
+ floatCount = 4 * 4;
+ else if (type == android.opengl.GLES20.GL_FLOAT_MAT3)
+ floatCount = 3 * 3;
+ else if (type == android.opengl.GLES20.GL_FLOAT_MAT2)
+ floatCount = 2 * 2;
+ else if ( type == android.opengl.GLES30.GL_FLOAT_MAT3x4
+ || type == android.opengl.GLES30.GL_FLOAT_MAT4x3)
+ {
+ floatCount = 4 * 3;
+ }
+
+ else if ( type == android.opengl.GLES20.GL_INT
+ || type == android.opengl.GLES20.GL_BOOL)
+ {
+ intCount = 1;
+ }
+
+ else if ( type == android.opengl.GLES20.GL_SAMPLER_2D
+ || type == android.opengl.GLES30.GL_SAMPLER_3D
+ || type == android.opengl.GLES20.GL_SAMPLER_CUBE)
+ {
+ kind = 'S';
+ }
+
+ else
+ {
+ throw new System.InvalidProgramException(
+ $"Effect: unsupported type {type:X8} in uniform '{name}'");
+ }
+
+ if (floatCount != 0)
+ {
+ var floatArray = new float[floatCount * size];
+ fixed (float* floatPointer = &floatArray[0])
+ values = (IntPtr) (void*) floatPointer;
+ storage = floatArray;
+ storageCopy = java.util.Arrays.copyOf(floatArray, floatArray.Length);
+ }
+
+ else if (intCount != 0)
+ {
+ var intArray = new int[intCount * size];
+ fixed (int* intPointer = &intArray[0])
+ values = (IntPtr) (void*) intPointer;
+ storage = intArray;
+ storageCopy = java.util.Arrays.copyOf(intArray, intArray.Length);
+ }
+
+ //GameRunner.Log($"EFFECT PARAMETER {Name} VALUES {values} STORAGE {this.storage}");
+ }
+
+ //
+ // bool value
+ //
+
+ public bool GetValueBoolean() => GetValueBooleanArray(1)[0];
+
+ public bool[] GetValueBooleanArray(int count)
+ {
+ CheckCount(count);
+ var intArray = IntArray(android.opengl.GLES20.GL_BOOL, count);
+ var value = new bool[count];
+ for (int i = 0; i < count; i++)
+ value[i] = intArray[i] != 0;
+ return value;
+ }
+
+ public void SetValue(bool[] value)
+ {
+ int count = value.Length;
+ var intArray = IntArray(android.opengl.GLES20.GL_BOOL, count);
+ for (int i = 0; i < count; i++)
+ intArray[i] = value[i] ? 1 : 0;
+ }
+
+ public void SetValue(bool value) => SetValue(new bool[] { value });
+
+ //
+ // int value
+ //
+
+ public int GetValueInt32() => GetValueInt32Array(1)[0];
+
+ public int[] GetValueInt32Array(int count)
+ {
+ CheckCount(count);
+ return java.util.Arrays.copyOf(
+ IntArray(android.opengl.GLES20.GL_INT, count), count);
+ }
+
+ public void SetValue(int[] value)
+ {
+ int count = value.Length;
+ var intArray = IntArray(android.opengl.GLES20.GL_INT, count);
+ for (int i = 0; i < count; i++)
+ intArray[i] = value[i];
+ }
+
+ public void SetValue(int value) => SetValue(new int[] { value });
+
+ //
+ // float value
+ //
+
+ public float GetValueSingle() => GetValueSingleArray(1)[0];
+
+ public float[] GetValueSingleArray(int count)
+ {
+ CheckCount(count);
+ return java.util.Arrays.copyOf(
+ FloatArray(android.opengl.GLES20.GL_FLOAT, count), count);
+ }
+
+ public void SetValue(float[] value)
+ {
+ int count = value.Length;
+ var floatArray = FloatArray(android.opengl.GLES20.GL_FLOAT, count);
+ for (int i = 0; i < count; i++)
+ floatArray[i] = value[i];
+ }
+
+ public void SetValue(float value) => SetValue(new float[] { value });
+
+ //
+ // Matrix
+ //
+
+ public Matrix GetValueMatrix() => GetValueMatrixArray(1)[0];
+
+ public Matrix[] GetValueMatrixArray(int count)
+ {
+ CheckCount(count);
+
+ int i = 0, j;
+ float[] floatArray;
+ // allocate an array of values without allocating each element
+ var value = (Matrix[]) java.lang.reflect.Array.newInstance(
+ (java.lang.Class) typeof(Matrix), count);
+ Matrix matrix;
+
+ if (type == android.opengl.GLES20.GL_FLOAT_MAT4)
+ {
+ floatArray = FloatArray(type, 4 * 4 * count);
+ for (j = 0; j < count; j++)
+ {
+ matrix.M11 = floatArray[i++]; // 0
+ matrix.M21 = floatArray[i++];
+ matrix.M31 = floatArray[i++];
+ matrix.M41 = floatArray[i++];
+ matrix.M12 = floatArray[i++]; // 4
+ matrix.M22 = floatArray[i++];
+ matrix.M32 = floatArray[i++];
+ matrix.M42 = floatArray[i++];
+ matrix.M13 = floatArray[i++]; // 8
+ matrix.M23 = floatArray[i++];
+ matrix.M33 = floatArray[i++];
+ matrix.M43 = floatArray[i++];
+ matrix.M14 = floatArray[i++]; // 12
+ matrix.M24 = floatArray[i++];
+ matrix.M34 = floatArray[i++];
+ matrix.M44 = floatArray[i++];
+ java.lang.reflect.Array.set(value, j, matrix); // matrix is cloned
+ }
+ }
+
+ else if (type == android.opengl.GLES20.GL_FLOAT_MAT3)
+ {
+ matrix.M41 = matrix.M42 = matrix.M43 =
+ matrix.M14 = matrix.M24 = matrix.M34 = matrix.M44 = 0;
+ floatArray = FloatArray(type, 3 * 3 * count);
+ for (j = 0; j < count; j++)
+ {
+ matrix.M11 = floatArray[i++]; // 0
+ matrix.M21 = floatArray[i++];
+ matrix.M31 = floatArray[i++];
+ matrix.M12 = floatArray[i++]; // 3
+ matrix.M22 = floatArray[i++];
+ matrix.M32 = floatArray[i++];
+ matrix.M13 = floatArray[i++]; // 6
+ matrix.M23 = floatArray[i++];
+ matrix.M33 = floatArray[i++];
+ java.lang.reflect.Array.set(value, j, matrix); // matrix is cloned
+ }
+ }
+
+ else
+ throw new InvalidCastException(Name + " MAT TYPE " + type);
+
+ return value;
+ }
+
+ public void SetValue(Matrix[] value)
+ {
+ int count = value.Length;
+
+ int i = 0, j;
+ float[] floatArray;
+ Matrix matrix;
+
+ if (type == android.opengl.GLES20.GL_FLOAT_MAT4)
+ {
+ floatArray = FloatArray(type, 4 * 4 * count);
+ for (j = 0; j < count; j++)
+ {
+ matrix = (Matrix) java.lang.reflect.Array.get(value, j);
+ floatArray[i++] = matrix.M11; // 0
+ floatArray[i++] = matrix.M21;
+ floatArray[i++] = matrix.M31;
+ floatArray[i++] = matrix.M41;
+ floatArray[i++] = matrix.M12; // 4
+ floatArray[i++] = matrix.M22;
+ floatArray[i++] = matrix.M32;
+ floatArray[i++] = matrix.M42;
+ floatArray[i++] = matrix.M13; // 8
+ floatArray[i++] = matrix.M23;
+ floatArray[i++] = matrix.M33;
+ floatArray[i++] = matrix.M43;
+ floatArray[i++] = matrix.M14; // 12
+ floatArray[i++] = matrix.M24;
+ floatArray[i++] = matrix.M34;
+ floatArray[i++] = matrix.M44;
+ }
+ }
+
+ else if (type == android.opengl.GLES20.GL_FLOAT_MAT3)
+ {
+ floatArray = FloatArray(type, 3 * 3 * count);
+ for (j = 0; j < count; j++)
+ {
+ matrix = (Matrix) java.lang.reflect.Array.get(value, j);
+ floatArray[i++] = matrix.M11; // 0
+ floatArray[i++] = matrix.M21;
+ floatArray[i++] = matrix.M31;
+ floatArray[i++] = matrix.M12; // 3
+ floatArray[i++] = matrix.M22;
+ floatArray[i++] = matrix.M32;
+ floatArray[i++] = matrix.M13; // 6
+ floatArray[i++] = matrix.M23;
+ floatArray[i++] = matrix.M33;
+ }
+ }
+
+ else
+ throw new InvalidCastException(Name + " MAT TYPE " + type);
+ }
+
+ public void SetValue(Matrix value)
+ {
+ // allocate an array of values without allocating each element
+ var array = (Matrix[]) java.lang.reflect.Array.newInstance(
+ (java.lang.Class) typeof(Matrix), 1);
+ java.lang.reflect.Array.set(array, 0, value);
+ SetValue(array);
+ }
+
+ //
+ // Vector3
+ //
+
+ public Vector3 GetValueVector3() => GetValueVector3Array(1)[0];
+
+ public Vector3[] GetValueVector3Array(int count)
+ {
+ CheckCount(count);
+ var floatArray = FloatArray(android.opengl.GLES20.GL_FLOAT_VEC3, 3 * count);
+
+ // allocate an array of values without allocating each element
+ var value = (Vector3[]) java.lang.reflect.Array.newInstance(
+ (java.lang.Class) typeof(Vector3), count);
+ Vector3 vector;
+
+ int i = 0;
+ for (int j = 0; j < count; j++)
+ {
+ vector.X = floatArray[i++];
+ vector.Y = floatArray[i++];
+ vector.Z = floatArray[i++];
+ java.lang.reflect.Array.set(value, j, vector); // vector is cloned
+ }
+ return value;
+ }
+
+ public void SetValue(Vector3[] value)
+ {
+ int count = value.Length;
+ var floatArray = FloatArray(android.opengl.GLES20.GL_FLOAT_VEC3, 3 * count);
+ Vector3 vector;
+ int i = 0;
+ for (int j = 0; j < count; j++)
+ {
+ vector = (Vector3) java.lang.reflect.Array.get(value, j);
+ floatArray[i++] = vector.X;
+ floatArray[i++] = vector.Y;
+ floatArray[i++] = vector.Z;
+ }
+ }
+
+ public void SetValue(Vector3 value)
+ {
+ // allocate an array of values without allocating each element
+ var array = (Vector3[]) java.lang.reflect.Array.newInstance(
+ (java.lang.Class) typeof(Vector3), 1);
+ java.lang.reflect.Array.set(array, 0, value);
+ SetValue(array);
+ }
+
+ //
+ // Vector4
+ //
+
+ public Vector4 GetValueVector4() => GetValueVector4Array(1)[0];
+
+ public Vector4[] GetValueVector4Array(int count)
+ {
+ CheckCount(count);
+ var floatArray = FloatArray(android.opengl.GLES20.GL_FLOAT_VEC4, 4 * count);
+
+ // allocate an array of values without allocating each element
+ var value = (Vector4[]) java.lang.reflect.Array.newInstance(
+ (java.lang.Class) typeof(Vector4), count);
+ Vector4 vector;
+
+ int i = 0;
+ for (int j = 0; j < count; j++)
+ {
+ vector.X = floatArray[i++];
+ vector.Y = floatArray[i++];
+ vector.Z = floatArray[i++];
+ vector.W = floatArray[i++];
+ java.lang.reflect.Array.set(value, j, vector); // vector is cloned
+ }
+ return value;
+ }
+
+ public void SetValue(Vector4[] value)
+ {
+ int count = value.Length;
+ var floatArray = FloatArray(android.opengl.GLES20.GL_FLOAT_VEC4, 4 * count);
+ Vector4 vector;
+ int i = 0;
+ for (int j = 0; j < count; j++)
+ {
+ vector = (Vector4) java.lang.reflect.Array.get(value, j);
+ floatArray[i++] = vector.X;
+ floatArray[i++] = vector.Y;
+ floatArray[i++] = vector.Z;
+ floatArray[i++] = vector.W;
+ }
+ }
+
+ public void SetValue(Vector4 value)
+ {
+ // allocate an array of values without allocating each element
+ var array = (Vector4[]) java.lang.reflect.Array.newInstance(
+ (java.lang.Class) typeof(Vector4), 1);
+ java.lang.reflect.Array.set(array, 0, value);
+ SetValue(array);
+ }
+
+ //
+ // texture
+ //
+
+ public Texture2D GetValueTexture2D() => (Texture2D) storage;
+
+ public Texture3D GetValueTexture3D() => (Texture3D) storage;
+
+ public TextureCube GetValueTextureCube() => (TextureCube) storage;
+
+ public void SetValue(Texture value)
+ {
+ if ( type == android.opengl.GLES20.GL_SAMPLER_2D
+ || type == android.opengl.GLES30.GL_SAMPLER_3D
+ || type == android.opengl.GLES20.GL_SAMPLER_CUBE)
+ {
+ storage = value;
+ }
+ }
+
+ //
+ // string
+ //
+
+ public void SetValue(string value) => throw new NotImplementedException(Name);
+
+ //
+ // Array access
+ //
+
+ int[] IntArray(int checkType, int checkCount)
+ {
+ if (type != checkType)
+ throw new InvalidCastException();
+ var intArray = (int[]) storage;
+ if (intArray.Length < checkCount)
+ throw new InvalidCastException();
+ return intArray;
+ }
+
+ float[] FloatArray(int checkType, int checkCount)
+ {
+ if (type != checkType)
+ throw new InvalidCastException(Name);
+ var floatArray = (float[]) storage;
+ if (floatArray.Length < checkCount)
+ throw new InvalidCastException(Name);
+ return floatArray;
+ }
+
+ void CheckCount(int count)
+ {
+ if (count <= 0)
+ throw new ArgumentOutOfRangeException(Name);
+ }
+
+ //
+ // Apply
+ //
+
+ public bool Apply(int uni, GraphicsDevice graphicsDevice)
+ {
+ if (storage is float[] floatArray)
+ {
+ if (kind == 'Y')
+ return ApplyMultiplierY(floatArray, uni, graphicsDevice);
+ else
+ return Apply(uni, floatArray);
+ }
+
+ if (storage is int[] intArray)
+ return Apply(uni, intArray);
+
+ if (kind == 'S')
+ {
+ if (storage != null)
+ {
+ // note that the uniform sampler (i.e. the texture
+ // unit selector) always remains set to default value
+ graphicsDevice.Textures[0] = (Texture) storage;
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool Apply(int uni, float[] floatArray)
+ {
+ var floatArrayCopy = (float[]) storageCopy;
+ if (! java.util.Arrays.@equals(floatArray, floatArrayCopy))
+ {
+ int num = floatArray.Length;
+ for (int i = 0; i < num; i++)
+ floatArrayCopy[i] = floatArray[i];
+
+ if (type == GLES20.GL_FLOAT)
+ GLES20.glUniform1fv(uni, num, floatArray, 0);
+ else if (type == GLES20.GL_FLOAT_VEC2)
+ GLES20.glUniform2fv(uni, num / 2, floatArray, 0);
+ else if (type == GLES20.GL_FLOAT_VEC3)
+ GLES20.glUniform3fv(uni, num / 3, floatArray, 0);
+ else if (type == GLES20.GL_FLOAT_VEC4)
+ GLES20.glUniform4fv(uni, num / 4, floatArray, 0);
+ else if (type == GLES20.GL_FLOAT_MAT4)
+ GLES20.glUniformMatrix4fv(uni, num / 16, true, floatArray, 0);
+ else if (type == GLES20.GL_FLOAT_MAT3)
+ GLES20.glUniformMatrix3fv(uni, num / 9, true, floatArray, 0);
+ else
+ return false;
+ }
+ return true;
+ }
+
+ private bool Apply(int uni, int[] intArray)
+ {
+ var intArrayCopy = (int[]) storageCopy;
+ if (! java.util.Arrays.@equals(intArray, intArrayCopy))
+ {
+ int num = intArray.Length;
+ for (int i = 0; i < num; i++)
+ intArrayCopy[i] = intArray[i];
+
+ if ( type == android.opengl.GLES20.GL_INT
+ || type == android.opengl.GLES20.GL_BOOL
+ || type == android.opengl.GLES20.GL_SAMPLER_2D)
+ {
+ GLES20.glUniform1iv(uni, num, intArray, 0);
+ }
+ else
+ return false;
+ }
+ return true;
+ }
+
+ private bool ApplyMultiplierY(float[] floatArray, int uni, GraphicsDevice graphicsDevice)
+ {
+ // when rendering to texture, we have to flip vertically
+ var floatValue = FNA3D.IsRenderToTexture(graphicsDevice) ? -1f : 1f;
+ if (floatValue != floatArray[0])
+ {
+ GLES20.glUniform1f(uni, floatValue);
+ floatArray[0] = floatValue;
+ }
+ return true;
+ }
+
+ //
+ // data
+ //
+
+ object storage;
+ object storageCopy;
+ int type;
+ char kind;
+
+ public IntPtr values;
+ public string Name { get; private set; }
+ }
+
+}
diff --git a/BNA/src/Empty.cs b/BNA/src/Empty.cs
new file mode 100644
index 0000000..88d8b40
--- /dev/null
+++ b/BNA/src/Empty.cs
@@ -0,0 +1,27 @@
+
+namespace Microsoft.Xna.Framework
+{
+
+ public class LaunchParameters
+ {
+ }
+
+ // this forwards audio events
+
+ public static class FrameworkDispatcher
+ {
+
+ public static void Update()
+ {
+ }
+
+ }
+
+ public static class FNAInternalExtensions
+ {
+ public static bool TryGetBuffer(System.IO.MemoryStream stream, ref byte[] buffer)
+ {
+ return false;
+ }
+ }
+}
diff --git a/BNA/src/FNA3D.cs b/BNA/src/FNA3D.cs
new file mode 100644
index 0000000..20ba433
--- /dev/null
+++ b/BNA/src/FNA3D.cs
@@ -0,0 +1,577 @@
+
+using System;
+using android.opengl;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ public static partial class FNA3D
+ {
+
+ //
+ // FNA3D_SetViewport
+ //
+
+ public static void FNA3D_SetViewport(IntPtr device, ref FNA3D_Viewport viewport)
+ {
+ var renderer = Renderer.Get(device);
+ var state = (State) renderer.UserData;
+ var v = viewport;
+
+ if ( state.AdjustViewport && (! state.RenderToTexture)
+ && v.x == 0 && v.y == 0 && v.w > 0 && v.h > 0
+ && v.w == state.BackBufferWidth && v.h == state.BackBufferHeight)
+ {
+ var (s_w, s_h) = (renderer.SurfaceWidth, renderer.SurfaceHeight);
+ if (v.w >= v.h)
+ {
+ // adjust from virtual landscape
+ v.h = (int) ((v.h * s_w) / (float) v.w);
+ v.w = s_w;
+ v.y = (s_h - v.h) / 2;
+ }
+ else
+ {
+ // adjust from virtual portrait
+ v.w = (int) ((v.w * s_w) / (float) v.h);
+ v.h = s_h;
+ v.x = (s_w - v.w) / 2;
+ }
+ }
+
+ Renderer.Get(device).Send( () =>
+ {
+ GLES20.glViewport(v.x, v.y, v.w, v.h);
+ GLES20.glDepthRangef(v.minDepth, v.maxDepth);
+ });
+ }
+
+ //
+ // FNA3D_SetScissorRect
+ //
+
+ public static void FNA3D_SetScissorRect(IntPtr device, ref Rectangle scissor)
+ {
+ var s = scissor;
+ Renderer.Get(device).Send( () =>
+ {
+ GLES20.glScissor(s.X, s.Y, s.Width, s.Height);
+ });
+ }
+
+ //
+ // FNA3D_Clear
+ //
+
+ public static void FNA3D_Clear(IntPtr device, ClearOptions options, ref Vector4 color,
+ float depth, int stencil)
+ {
+ var clearColor = color;
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ var state = (State) renderer.UserData;
+ var WriteMask = state.WriteMask;
+
+ if (state.ScissorTest)
+ {
+ // disable scissor before clear
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ }
+
+ bool restoreColorMask = false;
+ bool restoreDepthMask = false;
+ bool restoreStencilMask = false;
+
+ int mask = 0;
+ if ((options & ClearOptions.Target) != 0)
+ {
+ mask |= GLES20.GL_COLOR_BUFFER_BIT;
+
+ if (clearColor != state.ClearColor)
+ {
+ state.ClearColor = clearColor;
+ GLES20.glClearColor(
+ clearColor.X, clearColor.Y, clearColor.Z, clearColor.W);
+ }
+
+ if ((WriteMask & (RED_MASK | GREEN_MASK | BLUE_MASK | ALPHA_MASK))
+ != (RED_MASK | GREEN_MASK | BLUE_MASK | ALPHA_MASK))
+ {
+ // reset color masks before clear
+ GLES20.glColorMask(true, true, true, true);
+ restoreColorMask = true;
+ }
+ }
+
+ if ((options & ClearOptions.DepthBuffer) != 0)
+ {
+ mask |= GLES20.GL_DEPTH_BUFFER_BIT;
+
+ if (depth != state.ClearDepth)
+ {
+ state.ClearDepth = depth;
+ GLES20.glClearDepthf(depth);
+ }
+
+ if ((state.WriteMask & DEPTH_MASK) == 0)
+ {
+ // reset depth mask before clear
+ GLES20.glDepthMask(true);
+ restoreDepthMask = true;
+ }
+ }
+
+ if ((options & ClearOptions.Stencil) != 0)
+ {
+ mask |= GLES20.GL_STENCIL_BUFFER_BIT;
+
+ if (stencil != state.ClearStencil)
+ {
+ state.ClearStencil = stencil;
+ GLES20.glClearStencil(stencil);
+ }
+
+ if ((WriteMask & STENCIL_MASK) == 0)
+ {
+ // reset stencil mask before clear
+ GLES20.glStencilMask(-1);
+ restoreStencilMask = true;
+ }
+ }
+
+ GLES20.glClear(mask);
+
+ if (restoreStencilMask)
+ GLES20.glStencilMask(WriteMask & STENCIL_MASK);
+
+ if (restoreDepthMask)
+ GLES20.glDepthMask(false);
+
+ if (restoreColorMask)
+ {
+ GLES20.glColorMask((WriteMask & RED_MASK) != 0 ? true : false,
+ (WriteMask & GREEN_MASK) != 0 ? true : false,
+ (WriteMask & BLUE_MASK) != 0 ? true : false,
+ (WriteMask & ALPHA_MASK) != 0 ? true : false);
+ }
+
+ if (state.ScissorTest)
+ {
+ // restore scissor after clear
+ GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+ }
+ });
+ }
+
+ //
+ // FNA3D_SwapBuffers
+ //
+
+ public static void FNA3D_SwapBuffers(IntPtr device,
+ IntPtr sourceRectangle,
+ IntPtr destinationRectangle,
+ IntPtr overrideWindowHandle)
+ {
+ if ( (long) sourceRectangle != 0
+ || (long) destinationRectangle != 0
+ || (long) overrideWindowHandle != 0)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ var renderer = Renderer.Get(device);
+ renderer.Present();
+ }
+
+ //
+ // FNA3D_SetBlendState
+ //
+
+ public static void FNA3D_SetBlendState(IntPtr device, ref FNA3D_BlendState blendState)
+ {
+ var input = blendState;
+ var renderer = Renderer.Get(device);
+ Renderer.Get(device).Send( () =>
+ {
+ var state = (State) renderer.UserData;
+
+ if ( input.colorSourceBlend != Blend.One
+ || input.colorDestinationBlend != Blend.Zero
+ || input.alphaSourceBlend != Blend.One
+ || input.alphaDestinationBlend != Blend.Zero)
+ {
+ //
+ // blend state
+ //
+
+ if (! state.BlendEnable)
+ {
+ state.BlendEnable = true;
+ GLES20.glEnable(GLES20.GL_BLEND);
+ }
+
+ //
+ // XNA blend factor / GL blend color
+ //
+
+ if (input.blendFactor != state.BlendColor)
+ {
+ state.BlendColor = input.blendFactor;
+
+ GLES20.glBlendColor(state.BlendColor.R / 255f, state.BlendColor.G / 255f,
+ state.BlendColor.B / 255f, state.BlendColor.A / 255f);
+ }
+
+ //
+ // XNA blend mode / GL blend function
+ //
+
+ if ( input.colorSourceBlend != state.BlendSrcColor
+ || input.colorDestinationBlend != state.BlendDstColor
+ || input.alphaSourceBlend != state.BlendSrcAlpha
+ || input.alphaDestinationBlend != state.BlendDstAlpha)
+ {
+ state.BlendSrcColor = input.colorSourceBlend;
+ state.BlendDstColor = input.colorDestinationBlend;
+ state.BlendSrcAlpha = input.alphaSourceBlend;
+ state.BlendDstAlpha = input.alphaDestinationBlend;
+
+ GLES20.glBlendFuncSeparate(
+ BlendModeToBlendFunc[(int) state.BlendSrcColor],
+ BlendModeToBlendFunc[(int) state.BlendDstColor],
+ BlendModeToBlendFunc[(int) state.BlendSrcAlpha],
+ BlendModeToBlendFunc[(int) state.BlendDstAlpha]);
+ }
+
+ //
+ // XNA blend function / GL blend equation
+ //
+
+ if ( input.colorBlendFunction != state.BlendFuncColor
+ || input.alphaBlendFunction != state.BlendFuncAlpha)
+ {
+ state.BlendFuncColor = input.colorBlendFunction;
+ state.BlendFuncAlpha = input.alphaBlendFunction;
+
+ GLES20.glBlendEquationSeparate(
+ BlendFunctionToBlendEquation[(int) state.BlendFuncColor],
+ BlendFunctionToBlendEquation[(int) state.BlendFuncAlpha]);
+ }
+
+ //
+ // color write mask
+ //
+
+ bool inputRed = ((input.colorWriteEnable & ColorWriteChannels.Red) != 0);
+ bool inputGreen = ((input.colorWriteEnable & ColorWriteChannels.Green) != 0);
+ bool inputBlue = ((input.colorWriteEnable & ColorWriteChannels.Blue) != 0);
+ bool inputAlpha = ((input.colorWriteEnable & ColorWriteChannels.Alpha) != 0);
+ var WriteMask = state.WriteMask;
+
+ if ( inputRed != ((WriteMask & RED_MASK) != 0)
+ || inputGreen != ((WriteMask & GREEN_MASK) != 0)
+ || inputBlue != ((WriteMask & BLUE_MASK) != 0)
+ || inputAlpha != ((WriteMask & ALPHA_MASK) != 0))
+ {
+ state.WriteMask = (inputRed ? RED_MASK : 0)
+ | (inputGreen ? GREEN_MASK : 0)
+ | (inputBlue ? BLUE_MASK : 0)
+ | (inputAlpha ? ALPHA_MASK : 0);
+
+ GLES20.glColorMask(inputRed, inputGreen, inputBlue, inputAlpha);
+ }
+ }
+ else
+ {
+ state.BlendEnable = false;
+ GLES20.glDisable(GLES20.GL_BLEND);
+ }
+ });
+ }
+
+ static int[] BlendModeToBlendFunc = new int[]
+ {
+ GLES20.GL_ONE, // Blend.One
+ GLES20.GL_ZERO, // Blend.Zero
+ GLES20.GL_SRC_COLOR, // Blend.SourceColor
+ GLES20.GL_ONE_MINUS_SRC_COLOR, // Blend.InverseSourceColor
+ GLES20.GL_SRC_ALPHA, // Blend.SourceAlpha
+ GLES20.GL_ONE_MINUS_SRC_ALPHA, // Blend.InverseSourceAlpha
+ GLES20.GL_DST_COLOR, // Blend.DestinationColor
+ GLES20.GL_ONE_MINUS_DST_COLOR, // Blend.InverseDestinationColor
+ GLES20.GL_DST_ALPHA, // Blend.DestinationAlpha
+ GLES20.GL_ONE_MINUS_DST_ALPHA, // Blend.InverseDestinationAlpha
+ GLES20.GL_CONSTANT_COLOR, // Blend.BlendFactor
+ GLES20.GL_ONE_MINUS_CONSTANT_COLOR, // Blend.InverseBlendFactor
+ GLES20.GL_SRC_ALPHA_SATURATE // Blend.SourceAlphaSaturation
+ };
+
+ static int[] BlendFunctionToBlendEquation = new int[]
+ {
+ GLES20.GL_FUNC_ADD, // BlendFunction.Add
+ GLES20.GL_FUNC_SUBTRACT, // BlendFunction.Subtract
+ GLES20.GL_FUNC_REVERSE_SUBTRACT, // BlendFunction.ReverseSubtract
+ GLES30.GL_MAX, // BlendFunction.Max
+ GLES30.GL_MIN // BlendFunction.Min
+ };
+
+ //
+ // FNA3D_SetDepthStencilState
+ //
+
+ public static void FNA3D_SetDepthStencilState(IntPtr device,
+ ref FNA3D_DepthStencilState depthStencilState)
+ {
+ // AndroidActivity.LogI(">>>>> SET DEPTH AND STENCIL STATE");
+ }
+
+ //
+ // FNA3D_ApplyRasterizerState
+ //
+
+ public static void FNA3D_ApplyRasterizerState(IntPtr device,
+ ref FNA3D_RasterizerState rasterizerState)
+ {
+ var input = rasterizerState;
+ var renderer = Renderer.Get(device);
+ Renderer.Get(device).Send( () =>
+ {
+ var state = (State) renderer.UserData;
+
+ var inputScissorTest = (input.scissorTestEnable != 0);
+ if (inputScissorTest != state.ScissorTest)
+ {
+ state.ScissorTest = inputScissorTest;
+ if (inputScissorTest)
+ GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
+ else
+ GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
+ }
+
+ int inputCullMode;
+ if (state.RenderToTexture)
+ {
+ // select culling mode when rendering to texture, where we
+ // flip vertically; see also EffectParameter::ApplyMultiplierY
+ inputCullMode =
+ (input.cullMode == CullMode.CullCounterClockwiseFace) ? GLES20.GL_BACK
+ : (input.cullMode == CullMode.CullClockwiseFace) ? GLES20.GL_FRONT : 0;
+ }
+ else
+ {
+ // select culling mode when rendering directly to the screen
+ inputCullMode =
+ (input.cullMode == CullMode.CullCounterClockwiseFace) ? GLES20.GL_FRONT
+ : (input.cullMode == CullMode.CullClockwiseFace) ? GLES20.GL_BACK : 0;
+ }
+ if (inputCullMode != state.CullMode)
+ {
+ state.CullMode = inputCullMode;
+ if (inputCullMode == 0)
+ GLES20.glDisable(GLES20.GL_CULL_FACE);
+ else
+ {
+ GLES20.glEnable(GLES20.GL_CULL_FACE);
+ GLES20.glCullFace(inputCullMode);
+ }
+ }
+
+ // fillMode (glPolygonMode) is not supported on GL ES,
+ // so we also ignore depthBias (glPolygonOffset)
+ });
+ }
+
+ public static int FNA3D_GetMaxMultiSampleCount(IntPtr device, SurfaceFormat format,
+ int preferredMultiSampleCount)
+ {
+ return 0;
+
+ #if false
+ for (int i = 0; i < 21; i++)
+ GLES30.glGetInternalformativ(GLES20.GL_RENDERBUFFER,
+ SurfaceFormatToTextureInternalFormat[i],
+ GLES20.GL_SAMPLES, 1, count, 0);
+ if (GLES20.glGetError() == 0 && count[0] < preferredMultiSampleCount)
+ preferredMultiSampleCount = count[0];
+ #endif
+ }
+
+ //
+ // FNA3D_DrawIndexedPrimitives
+ //
+
+ public static void FNA3D_DrawIndexedPrimitives(IntPtr device, PrimitiveType primitiveType,
+ int baseVertex, int minVertexIndex,
+ int numVertices, int startIndex,
+ int primitiveCount, IntPtr indices,
+ IndexElementSize indexElementSize)
+ {
+ int elementSize, elementType;
+ if (indexElementSize == IndexElementSize.SixteenBits)
+ {
+ elementSize = 2;
+ elementType = GLES20.GL_UNSIGNED_SHORT;
+ }
+ else if (indexElementSize == IndexElementSize.ThirtyTwoBits)
+ {
+ elementSize = 4;
+ elementType = GLES20.GL_UNSIGNED_INT;
+ }
+ else
+ throw new ArgumentException("invalid IndexElementSize");
+
+ int drawMode = PrimitiveTypeToDrawMode[(int) primitiveType];
+ int maxVertexIndex = minVertexIndex + numVertices - 1;
+ int indexOffset = startIndex * elementSize;
+ primitiveCount = PrimitiveCount(primitiveType, primitiveCount);
+
+ Renderer.Get(device).Send( () =>
+ {
+ GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, (int) indices);
+ GLES30.glDrawRangeElements(drawMode, minVertexIndex, maxVertexIndex,
+ primitiveCount, elementType, indexOffset);
+ });
+ }
+
+ static int[] PrimitiveTypeToDrawMode = new int[]
+ {
+ GLES20.GL_TRIANGLES, // PrimitiveType.TriangleList
+ GLES20.GL_TRIANGLE_STRIP, // PrimitiveType.TriangleStrip
+ GLES20.GL_LINES, // PrimitiveType.LineList
+ GLES20.GL_LINE_STRIP, // PrimitiveType.LineStrip
+ };
+
+ static int PrimitiveCount(PrimitiveType primitiveType, int primitiveCount)
+ {
+ return primitiveType switch
+ {
+ PrimitiveType.TriangleList => primitiveCount * 3,
+ PrimitiveType.TriangleStrip => primitiveCount + 2,
+ PrimitiveType.LineList => primitiveCount * 2,
+ PrimitiveType.LineStrip => primitiveCount + 1,
+ _ => throw new ArgumentException("invalid PrimitiveType"),
+ };
+ }
+
+ //
+ // FNA3D_DrawPrimitives
+ //
+
+ public static void FNA3D_DrawPrimitives(IntPtr device, PrimitiveType primitiveType,
+ int vertexStart, int primitiveCount)
+ {
+ int drawMode = PrimitiveTypeToDrawMode[(int) primitiveType];
+ primitiveCount = PrimitiveCount(primitiveType, primitiveCount);
+
+ Renderer.Get(device).Send( () =>
+ {
+ GLES20.glDrawArrays(drawMode, vertexStart, primitiveCount);
+ });
+ }
+
+ //
+ // FNA3D_BlendState
+ //
+
+ public struct FNA3D_BlendState
+ {
+ public Blend colorSourceBlend;
+ public Blend colorDestinationBlend;
+ public BlendFunction colorBlendFunction;
+ public Blend alphaSourceBlend;
+ public Blend alphaDestinationBlend;
+ public BlendFunction alphaBlendFunction;
+ public ColorWriteChannels colorWriteEnable;
+ public ColorWriteChannels colorWriteEnable1;
+ public ColorWriteChannels colorWriteEnable2;
+ public ColorWriteChannels colorWriteEnable3;
+ public Color blendFactor;
+ public int multiSampleMask;
+ }
+
+ //
+ // FNA3D_DepthStencilState
+ //
+
+ public struct FNA3D_DepthStencilState
+ {
+ public byte depthBufferEnable;
+ public byte depthBufferWriteEnable;
+ public CompareFunction depthBufferFunction;
+ public byte stencilEnable;
+ public int stencilMask;
+ public int stencilWriteMask;
+ public byte twoSidedStencilMode;
+ public StencilOperation stencilFail;
+ public StencilOperation stencilDepthBufferFail;
+ public StencilOperation stencilPass;
+ public CompareFunction stencilFunction;
+ public StencilOperation ccwStencilFail;
+ public StencilOperation ccwStencilDepthBufferFail;
+ public StencilOperation ccwStencilPass;
+ public CompareFunction ccwStencilFunction;
+ public int referenceStencil;
+ }
+
+ //
+ // FNA3D_RasterizerState
+ //
+
+ public struct FNA3D_RasterizerState
+ {
+ public FillMode fillMode;
+ public CullMode cullMode;
+ public float depthBias;
+ public float slopeScaleDepthBias;
+ public byte scissorTestEnable;
+ public byte multiSampleAntiAlias;
+ }
+
+ //
+ // FNA3D_Viewport
+ //
+
+ public struct FNA3D_Viewport
+ {
+ public int x;
+ public int y;
+ public int w;
+ public int h;
+ public float minDepth;
+ public float maxDepth;
+ }
+
+ //
+ // State
+ //
+
+ private partial class State
+ {
+ public Vector4 ClearColor;
+ public float ClearDepth;
+ public int ClearStencil;
+ public int WriteMask = -1;
+ public int CullMode;
+ public bool ScissorTest;
+
+ public bool BlendEnable;
+ public Color BlendColor;
+
+ public Blend BlendSrcColor;
+ public Blend BlendDstColor = Blend.Zero;
+ public Blend BlendSrcAlpha;
+ public Blend BlendDstAlpha = Blend.Zero;
+ public BlendFunction BlendFuncColor;
+ public BlendFunction BlendFuncAlpha;
+ }
+
+ private const int DEPTH_MASK = 0x40000000;
+ private const int ALPHA_MASK = 0x20000000;
+ private const int BLUE_MASK = 0x04000000;
+ private const int GREEN_MASK = 0x02000000;
+ private const int RED_MASK = 0x01000000;
+ private const int STENCIL_MASK = 0x00FFFFFF;
+ }
+
+}
diff --git a/BNA/src/FNA3D_Buf.cs b/BNA/src/FNA3D_Buf.cs
new file mode 100644
index 0000000..428e03f
--- /dev/null
+++ b/BNA/src/FNA3D_Buf.cs
@@ -0,0 +1,498 @@
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using android.opengl;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ public static partial class FNA3D
+ {
+
+ //
+ // FNA3D_SupportsNoOverwrite
+ //
+
+ public static byte FNA3D_SupportsNoOverwrite(IntPtr device)
+ {
+ // prevent flag SetDataOptions.NoOverwrite in Set*BufferData calls
+ return 0;
+ }
+
+
+
+ //
+ // Create Buffers
+ //
+
+ private static int CreateBuffer(Renderer renderer, int target, byte dynamic, int size)
+ {
+ int bufferId = 0;
+ renderer.Send( () =>
+ {
+ int[] id = new int[1];
+ GLES20.glGenBuffers(1, id, 0);
+ if (id[0] != 0)
+ {
+ bufferId = id[0];
+ int usage = (dynamic != 0 ? GLES20.GL_STREAM_DRAW
+ : GLES20.GL_STATIC_DRAW);
+ GLES20.glBindBuffer(target, bufferId);
+ GLES20.glBufferData(target, size, null, usage);
+
+ var state = (State) renderer.UserData;
+ state.BufferSizeUsage[bufferId] = new int[] { size, usage };
+ }
+ });
+ return bufferId;
+ }
+
+ public static IntPtr FNA3D_GenVertexBuffer(IntPtr device, byte dynamic,
+ BufferUsage usage, int sizeInBytes)
+ {
+ return (IntPtr) CreateBuffer(Renderer.Get(device),
+ GLES20.GL_ARRAY_BUFFER,
+ dynamic, sizeInBytes);
+ }
+
+ public static IntPtr FNA3D_GenIndexBuffer(IntPtr device, byte dynamic,
+ BufferUsage usage, int sizeInBytes)
+ {
+ return (IntPtr) CreateBuffer(Renderer.Get(device),
+ GLES20.GL_ELEMENT_ARRAY_BUFFER,
+ dynamic, sizeInBytes);
+ }
+
+
+
+ //
+ // Delete Buffers
+ //
+
+ public static void FNA3D_AddDisposeVertexBuffer(IntPtr device, IntPtr buffer)
+ {
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ GLES20.glDeleteBuffers(1, new int[] { (int) buffer }, 0);
+
+ var state = (State) renderer.UserData;
+ state.BufferSizeUsage.Remove((int) buffer);
+ });
+ }
+
+ public static void FNA3D_AddDisposeIndexBuffer(IntPtr device, IntPtr buffer)
+ {
+ FNA3D_AddDisposeVertexBuffer(device, buffer);
+ }
+
+
+
+ //
+ // Set Buffer Data
+ //
+
+ private static void SetBufferData(Renderer renderer, int target, int bufferId,
+ int bufferOffset, bool discard,
+ IntPtr dataPointer, int dataLength)
+ {
+ var state = (State) renderer.UserData;
+ var dataBuffer = BufferSerializer.Convert(
+ dataPointer, dataLength, state, bufferId);
+
+ renderer.Send( () =>
+ {
+ GLES20.glBindBuffer(target, bufferId);
+
+ if (discard)
+ {
+ var sizeUsage = state.BufferSizeUsage[bufferId];
+ GLES20.glBufferData(target, sizeUsage[0], null, sizeUsage[1]);
+ }
+
+ GLES20.glBufferSubData(target, bufferOffset, dataLength, dataBuffer);
+ });
+ }
+
+ public static void FNA3D_SetVertexBufferData(IntPtr device, IntPtr buffer,
+ int offsetInBytes, IntPtr data,
+ int elementCount, int elementSizeInBytes,
+ int vertexStride, SetDataOptions options)
+ {
+ if (elementSizeInBytes != vertexStride)
+ throw new System.ArgumentException("elementSizeInBytes != vertexStride");
+
+ SetBufferData(Renderer.Get(device), GLES20.GL_ARRAY_BUFFER,
+ (int) buffer, offsetInBytes,
+ (options == SetDataOptions.Discard),
+ data, elementCount * elementSizeInBytes);
+ }
+
+ public static void FNA3D_SetIndexBufferData(IntPtr device, IntPtr buffer,
+ int offsetInBytes, IntPtr data,
+ int dataLength, SetDataOptions options)
+ {
+ SetBufferData(Renderer.Get(device), GLES20.GL_ELEMENT_ARRAY_BUFFER,
+ (int) buffer, offsetInBytes,
+ (options == SetDataOptions.Discard),
+ data, dataLength);
+ }
+
+
+
+ //
+ // Set Buffer Attributes
+ //
+
+ public static unsafe void FNA3D_ApplyVertexBufferBindings(IntPtr device,
+ FNA3D_VertexBufferBinding* bindings,
+ int numBindings, byte bindingsUpdated,
+ int baseVertex)
+ {
+ var bindingsCopy = new FNA3D_VertexBufferBinding[numBindings];
+ for (int i = 0; i < numBindings; i++)
+ bindingsCopy[i] = bindings[i];
+
+ Renderer.Get(device).Send( () =>
+ {
+ int nextAttribIndex = 0;
+ foreach (var binding in bindingsCopy)
+ {
+ if (binding.instanceFrequency != 0)
+ throw new ArgumentException("InstanceFrequnecy != 0");
+ GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, (int) binding.vertexBuffer);
+
+ var vertexDecl = binding.vertexDeclaration;
+ var elements = (VertexElement[]) System.Runtime.InteropServices.GCHandle
+ .FromIntPtr(vertexDecl.elements).Target;
+ for (int j = 0; j < vertexDecl.elementCount; j++)
+ {
+ if (elements[j].UsageIndex != 0)
+ throw new ArgumentException("UsageIndex != 0");
+ var fmt = elements[j].VertexElementFormat;
+
+ int size = VertexElementToBindingSize[(int) fmt];
+ int type = VertexElementToBindingType[(int) fmt];
+ bool norm = ( elements[j].VertexElementUsage ==
+ VertexElementUsage.Color
+ || fmt == VertexElementFormat.NormalizedShort2
+ || fmt == VertexElementFormat.NormalizedShort4);
+
+ int stride = vertexDecl.vertexStride;
+ int offset = (binding.vertexOffset + baseVertex) * stride
+ + elements[j].Offset;
+
+ GLES20.glVertexAttribPointer(nextAttribIndex, size, type, norm,
+ stride, offset);
+
+ GLES20.glEnableVertexAttribArray(nextAttribIndex++);
+ }
+ }
+ });
+ }
+
+ //
+ // FNA3D_VertexBufferBinding
+ //
+
+ public struct FNA3D_VertexBufferBinding
+ {
+ public IntPtr vertexBuffer;
+ public FNA3D_VertexDeclaration vertexDeclaration;
+ public int vertexOffset;
+ public int instanceFrequency;
+ }
+
+ //
+ // FNA3D_VertexDeclaration
+ //
+
+ public struct FNA3D_VertexDeclaration
+ {
+ public int vertexStride;
+ public int elementCount;
+ public IntPtr elements;
+ }
+
+ //
+ // VertexElementToBindingSize
+ //
+
+ static int[] VertexElementToBindingSize = new int[]
+ {
+ 1, // VertexElementFormat.Single
+ 2, // VertexElementFormat.Vector2
+ 3, // VertexElementFormat.Vector3
+ 4, // VertexElementFormat.Vector4
+ 4, // VertexElementFormat.Color
+ 4, // VertexElementFormat.Byte4
+ 2, // VertexElementFormat.Short2
+ 4, // VertexElementFormat.Short4
+ 2, // VertexElementFormat.NormalizedShort2
+ 4, // VertexElementFormat.NormalizedShort4
+ 2, // VertexElementFormat.HalfVector2
+ 4 // VertexElementFormat.HalfVector4
+ };
+
+ //
+ // VertexElementToBindingType
+ //
+
+ static int[] VertexElementToBindingType = new int[]
+ {
+ GLES20.GL_FLOAT, // VertexElementFormat.Single
+ GLES20.GL_FLOAT, // VertexElementFormat.Vector2
+ GLES20.GL_FLOAT, // VertexElementFormat.Vector3
+ GLES20.GL_FLOAT, // VertexElementFormat.Vector4
+ GLES20.GL_UNSIGNED_BYTE, // VertexElementFormat.Color
+ GLES20.GL_UNSIGNED_BYTE, // VertexElementFormat.Byte4
+ GLES20.GL_SHORT, // VertexElementFormat.Short2
+ GLES20.GL_SHORT, // VertexElementFormat.Short4
+ GLES20.GL_SHORT, // VertexElementFormat.NormalizedShort2
+ GLES20.GL_SHORT, // VertexElementFormat.NormalizedShort4
+ GLES30.GL_HALF_FLOAT, // VertexElementFormat.HalfVector2
+ GLES30.GL_HALF_FLOAT // VertexElementFormat.HalfVector4
+ };
+
+
+
+ //
+ // BufferSerializer
+ //
+
+ private static class BufferSerializer
+ {
+
+ public static java.nio.Buffer Convert(IntPtr data, int length,
+ State state, int bufferId)
+ {
+ java.nio.Buffer oldBuffer, newBuffer;
+ lock (state.BufferCache)
+ {
+ state.BufferCache.TryGetValue(bufferId, out oldBuffer);
+ }
+
+ // FNA IndexBuffer uses GCHandle::Alloc and GCHandle::AddrOfPinnedObject.
+ // we use GCHandle::FromIntPtr to convert that address to an object reference.
+ // see also: system.runtime.interopservices.GCHandle struct in baselib.
+ int offset = (int) data;
+ newBuffer = Convert(GCHandle.FromIntPtr(data - offset).Target,
+ offset, length, oldBuffer);
+
+ if (newBuffer != oldBuffer)
+ {
+ lock (state.BufferCache)
+ {
+ state.BufferCache[bufferId] = newBuffer;
+ }
+ }
+
+ return newBuffer;
+ }
+
+
+ public static java.nio.Buffer Convert(object data, int offset, int length,
+ java.nio.Buffer buffer)
+ {
+ if (data is short[])
+ {
+ return FromShort((short[]) data, offset, length);
+ }
+
+ var byteBuffer = (buffer != null && buffer.limit() >= length)
+ ? (java.nio.ByteBuffer) buffer
+ : java.nio.ByteBuffer.allocateDirect(length)
+ .order(java.nio.ByteOrder.nativeOrder());
+
+ if (data is SpriteBatch.VertexPositionColorTexture4[])
+ {
+ FromVertexPositionColorTexture4(
+ (SpriteBatch.VertexPositionColorTexture4[]) data,
+ offset, length, byteBuffer);
+ }
+
+ else if (data is VertexPositionColor[])
+ {
+ FromVertexPositionColor(
+ (VertexPositionColor[]) data, offset, length, byteBuffer);
+ }
+
+ else if (data is VertexPositionColorTexture[])
+ {
+ FromVertexPositionColorTexture(
+ (VertexPositionColorTexture[]) data, offset, length, byteBuffer);
+ }
+
+ else if (data is VertexPositionNormalTexture[])
+ {
+ FromVertexPositionNormalTexture(
+ (VertexPositionNormalTexture[]) data, offset, length, byteBuffer);
+ }
+
+ else if (data is VertexPositionTexture[])
+ {
+ FromVertexPositionTexture(
+ (VertexPositionTexture[]) data, offset, length, byteBuffer);
+ }
+
+ else
+ {
+ /*IVertexType iVertexType,
+ => FromVertexDeclaration(iVertexType.VertexDeclaration,
+ data, offset, length),*/
+
+ throw new ArgumentException($"unsupported buffer type '{data.GetType()}'");
+ };
+
+ return byteBuffer.position(0);
+ }
+
+ private static void ValidateOffsetAndLength(int offset, int length, int divisor)
+ {
+ if ((offset % divisor) != 0 || (length % divisor) != 0)
+ throw new ArgumentException(
+ $"length and offset of buffer should be divisible by {divisor}");
+ }
+
+ private static java.nio.Buffer FromShort(short[] array, int offset, int length)
+ {
+ ValidateOffsetAndLength(offset, length, 2);
+ return java.nio.ShortBuffer.wrap(array,
+ offset / sizeof(short),
+ length / sizeof(short));
+ }
+
+ private static void FromVertexPositionColorTexture4(
+ SpriteBatch.VertexPositionColorTexture4[] array,
+ int offset, int length, java.nio.ByteBuffer buffer)
+ {
+ ValidateOffsetAndLength(offset, length, 96);
+
+ int index = offset / 96;
+ int count = length / 96;
+ for (; count-- > 0; index++)
+ {
+ PutVector3(buffer, ref array[index].Position0);
+ PutColor(buffer, ref array[index].Color0);
+ PutVector2(buffer, ref array[index].TextureCoordinate0);
+
+ PutVector3(buffer, ref array[index].Position1);
+ PutColor(buffer, ref array[index].Color1);
+ PutVector2(buffer, ref array[index].TextureCoordinate1);
+
+ PutVector3(buffer, ref array[index].Position2);
+ PutColor(buffer, ref array[index].Color2);
+ PutVector2(buffer, ref array[index].TextureCoordinate2);
+
+ PutVector3(buffer, ref array[index].Position3);
+ PutColor(buffer, ref array[index].Color3);
+ PutVector2(buffer, ref array[index].TextureCoordinate3);
+ }
+ }
+
+ private static void FromVertexPositionColor(
+ VertexPositionColor[] array,
+ int offset, int length, java.nio.ByteBuffer buffer)
+ {
+ ValidateOffsetAndLength(offset, length, 16);
+
+ int index = offset / 16;
+ int count = length / 16;
+ for (; count-- > 0; index++)
+ {
+ PutVector3(buffer, ref array[index].Position);
+ PutColor(buffer, ref array[index].Color);
+ }
+ }
+
+ private static void FromVertexPositionColorTexture(
+ VertexPositionColorTexture[] array,
+ int offset, int length, java.nio.ByteBuffer buffer)
+ {
+ ValidateOffsetAndLength(offset, length, 24);
+
+ int index = offset / 24;
+ int count = length / 24;
+ for (; count-- > 0; index++)
+ {
+ PutVector3(buffer, ref array[index].Position);
+ PutColor(buffer, ref array[index].Color);
+ PutVector2(buffer, ref array[index].TextureCoordinate);
+ }
+ }
+
+ private static void FromVertexPositionNormalTexture(
+ VertexPositionNormalTexture[] array,
+ int offset, int length, java.nio.ByteBuffer buffer)
+ {
+ ValidateOffsetAndLength(offset, length, 32);
+
+ int index = offset / 32;
+ int count = length / 32;
+ for (; count-- > 0; index++)
+ {
+ PutVector3(buffer, ref array[index].Position);
+ PutVector3(buffer, ref array[index].Normal);
+ PutVector2(buffer, ref array[index].TextureCoordinate);
+ }
+ }
+
+ private static void FromVertexPositionTexture(
+ VertexPositionTexture[] array,
+ int offset, int length, java.nio.ByteBuffer buffer)
+ {
+ ValidateOffsetAndLength(offset, length, 20);
+
+ int index = offset / 20;
+ int count = length / 20;
+ for (; count-- > 0; index++)
+ {
+ PutVector3(buffer, ref array[index].Position);
+ PutVector2(buffer, ref array[index].TextureCoordinate);
+ }
+ }
+
+ /*private static java.nio.Buffer FromVertexDeclaration(
+ VertexDeclaration vertexDeclaration,
+ object data, int offset, int length) { } */
+
+ private static void PutVector3(java.nio.ByteBuffer buffer, ref Vector3 vector3)
+ {
+ buffer.putFloat(vector3.X);
+ buffer.putFloat(vector3.Y);
+ buffer.putFloat(vector3.Z);
+ }
+
+ private static void PutVector2(java.nio.ByteBuffer buffer, ref Vector2 vector2)
+ {
+ buffer.putFloat(vector2.X);
+ buffer.putFloat(vector2.Y);
+ }
+
+ private static void PutColor(java.nio.ByteBuffer buffer, ref Color color)
+ {
+ buffer.put((sbyte) color.R);
+ buffer.put((sbyte) color.G);
+ buffer.put((sbyte) color.B);
+ buffer.put((sbyte) color.A);
+ }
+
+ }
+
+
+
+ //
+ // State
+ //
+
+ private partial class State
+ {
+ public Dictionary BufferSizeUsage = new Dictionary();
+ public Dictionary BufferCache = new Dictionary();
+ }
+
+ }
+
+}
diff --git a/BNA/src/FNA3D_Dev.cs b/BNA/src/FNA3D_Dev.cs
new file mode 100644
index 0000000..9cabcf1
--- /dev/null
+++ b/BNA/src/FNA3D_Dev.cs
@@ -0,0 +1,140 @@
+
+using System;
+using android.opengl;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ public static partial class FNA3D
+ {
+
+ //
+ // FNA3D_CreateDevice
+ //
+
+ public static IntPtr FNA3D_CreateDevice(
+ ref FNA3D_PresentationParameters presentationParameters,
+ byte debugMode)
+ {
+ int depthSize, stencilSize = 0;
+ switch (presentationParameters.depthStencilFormat)
+ {
+ case DepthFormat.None: depthSize = 0; break;
+ case DepthFormat.Depth16: depthSize = 16; break;
+ case DepthFormat.Depth24: depthSize = 24; break;
+ case DepthFormat.Depth24Stencil8: depthSize = 24; stencilSize = 8; break;
+ default: throw new ArgumentException("depthStencilFormat");
+ }
+
+ var device = Renderer.Create(GameRunner.Singleton.Activity,
+ GameRunner.Singleton.OnSurfaceChanged,
+ 8, 8, 8, 0, depthSize, stencilSize);
+ FNA3D_ResetBackbuffer(device, ref presentationParameters);
+ return device;
+
+ /*var renderer = Renderer.Get(device);
+ renderer.UserData = new State()
+ {
+ BackBufferWidth = presentationParameters.backBufferWidth,
+ BackBufferHeight = presentationParameters.backBufferHeight,
+ AdjustViewport = // see also FNA3D_SetViewport
+ (presentationParameters.displayOrientation == DisplayOrientation.Default)
+ };*/
+ }
+
+ //
+ // FNA3D_ResetBackbuffer
+ //
+
+ public static void FNA3D_ResetBackbuffer(IntPtr device,
+ ref FNA3D_PresentationParameters presentationParameters)
+ {
+ var renderer = Renderer.Get(device);
+
+ var state = (State) renderer.UserData;
+ if (state == null)
+ {
+ state = new State();
+ renderer.UserData = state;
+ }
+
+ state.BackBufferWidth = presentationParameters.backBufferWidth;
+ state.BackBufferHeight = presentationParameters.backBufferHeight;
+ state.AdjustViewport = // see also FNA3D_SetViewport
+ (presentationParameters.displayOrientation == DisplayOrientation.Default);
+
+ presentationParameters.backBufferFormat = SurfaceFormat.Color;
+ presentationParameters.isFullScreen = 1;
+ }
+
+ //
+ // FNA3D_DestroyDevice
+ //
+
+ public static void FNA3D_DestroyDevice(IntPtr device)
+ {
+ Renderer.Get(device).Release();
+ }
+
+ //
+ // FNA3D_GetMaxTextureSlots
+ //
+
+ public static void FNA3D_GetMaxTextureSlots(IntPtr device,
+ out int textures, out int vertexTextures)
+ {
+ // XNA GraphicsDevice Limits from FNA3D/src/FNA3D_Driver.h
+ const int MAX_TEXTURE_SAMPLERS = 16;
+ const int MAX_VERTEXTEXTURE_SAMPLERS = 4;
+
+ var renderer = Renderer.Get(device);
+ int numSamplers = renderer.TextureUnits;
+ // number of texture slots
+ textures = Math.Min(numSamplers, MAX_TEXTURE_SAMPLERS);
+ // number of vertex texture slots
+ vertexTextures = Math.Min(Math.Max(numSamplers - MAX_TEXTURE_SAMPLERS, 0),
+ MAX_VERTEXTEXTURE_SAMPLERS);
+ }
+
+ //
+ // FNA3D_GetBackbufferDepthFormat
+ //
+
+ public static DepthFormat FNA3D_GetBackbufferDepthFormat(IntPtr device)
+ {
+ return Renderer.Get(device).SurfaceDepthFormat;
+ }
+
+ //
+ // FNA3D_PresentationParameters
+ //
+
+ public struct FNA3D_PresentationParameters
+ {
+ public int backBufferWidth;
+ public int backBufferHeight;
+ public SurfaceFormat backBufferFormat;
+ public int multiSampleCount;
+ public IntPtr deviceWindowHandle;
+ public byte isFullScreen;
+ public DepthFormat depthStencilFormat;
+ public PresentInterval presentationInterval;
+ public DisplayOrientation displayOrientation;
+ public RenderTargetUsage renderTargetUsage;
+ }
+
+ //
+ // State
+ //
+
+ private partial class State
+ {
+ public int BackBufferWidth;
+ public int BackBufferHeight;
+ public bool AdjustViewport;
+ }
+
+ }
+
+}
diff --git a/BNA/src/FNA3D_Rt.cs b/BNA/src/FNA3D_Rt.cs
new file mode 100644
index 0000000..959cc58
--- /dev/null
+++ b/BNA/src/FNA3D_Rt.cs
@@ -0,0 +1,247 @@
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using android.opengl;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ public static partial class FNA3D
+ {
+
+ //
+ // FNA3D_SetRenderTargets
+ //
+
+ public static unsafe void FNA3D_SetRenderTargets(IntPtr device,
+ FNA3D_RenderTargetBinding* renderTargets,
+ int numRenderTargets,
+ IntPtr depthStencilBuffer,
+ DepthFormat depthFormat,
+ byte preserveContents)
+ {
+ var renderTargetsCopy = new FNA3D_RenderTargetBinding[numRenderTargets];
+ for (int i = 0; i < numRenderTargets; i++)
+ renderTargetsCopy[i] = renderTargets[i];
+
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ var state = (State) renderer.UserData;
+ if (state.TargetFramebuffer == 0)
+ {
+ var id = new int[1];
+ GLES20.glGenFramebuffers(1, id, 0);
+ if ((state.TargetFramebuffer = id[0]) == 0)
+ return;
+ }
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.TargetFramebuffer);
+
+ int attachmentIndex = GLES20.GL_COLOR_ATTACHMENT0;
+ foreach (var renderTarget in renderTargetsCopy)
+ {
+ if (renderTarget.colorBuffer != IntPtr.Zero)
+ {
+ // a color buffer is only created if a non-zero result
+ // from FNA3D_GetMaxMultiSampleCount, which we never do
+ throw new PlatformNotSupportedException();
+ /*GLES20.glFramebufferRenderbuffer(
+ GLES20.GL_FRAMEBUFFER, attachmentIndex,
+ GLES20.GL_RENDERBUFFER, (int) renderTarget.colorBuffer);*/
+ }
+ else
+ {
+ int attachmentType = GLES20.GL_TEXTURE_2D;
+ if (renderTarget.type != /* FNA3D_RENDERTARGET_TYPE_2D */ 0)
+ {
+ attachmentType = GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X
+ + renderTarget.data2;
+ }
+ GLES20.glFramebufferTexture2D(
+ GLES20.GL_FRAMEBUFFER, attachmentIndex,
+ attachmentType, (int) renderTarget.texture, 0);
+ }
+ attachmentIndex++;
+ }
+
+ int lastAttachmentPlusOne = state.ActiveAttachments;
+ state.ActiveAttachments = attachmentIndex;
+ while (attachmentIndex < lastAttachmentPlusOne)
+ {
+ GLES20.glFramebufferRenderbuffer(
+ GLES20.GL_FRAMEBUFFER, attachmentIndex++,
+ GLES20.GL_RENDERBUFFER, 0);
+ }
+
+ GLES20.glFramebufferRenderbuffer(
+ GLES20.GL_FRAMEBUFFER, GLES30.GL_DEPTH_STENCIL_ATTACHMENT,
+ GLES20.GL_RENDERBUFFER, (int) depthStencilBuffer);
+
+ state.RenderToTexture = true;
+ });
+ }
+
+ //
+ // FNA3D_SetRenderTargets
+ //
+
+ public static void FNA3D_SetRenderTargets(IntPtr device,
+ IntPtr renderTargets,
+ int numRenderTargets,
+ IntPtr depthStencilBuffer,
+ DepthFormat depthFormat,
+ byte preserveContents)
+ {
+ if (renderTargets != IntPtr.Zero || numRenderTargets != 0)
+ throw new PlatformNotSupportedException();
+
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
+
+ var state = (State) renderer.UserData;
+ state.RenderToTexture = false;
+ });
+ }
+
+ //
+ // FNA3D_ResolveTarget
+ //
+
+ public static void FNA3D_ResolveTarget(IntPtr device,
+ ref FNA3D_RenderTargetBinding renderTarget)
+ {
+ if (renderTarget.multiSampleCount > 0)
+ {
+ // no support for multisampling; see FNA3D_SetRenderTargets
+ throw new PlatformNotSupportedException();
+ }
+
+ if (renderTarget.levelCount > 1)
+ {
+ int attachmentType = GLES20.GL_TEXTURE_2D;
+ if (renderTarget.type != /* FNA3D_RENDERTARGET_TYPE_2D */ 0)
+ {
+ attachmentType = GLES20.GL_TEXTURE_CUBE_MAP_POSITIVE_X
+ + renderTarget.data2;
+ }
+ int texture = (int) renderTarget.texture;
+
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ int textureUnit = GLES20.GL_TEXTURE0 + renderer.TextureUnits - 1;
+ GLES20.glActiveTexture(textureUnit);
+
+ GLES20.glBindTexture(attachmentType, texture);
+ GLES20.glGenerateMipmap(attachmentType);
+ GLES20.glBindTexture(attachmentType, 0);
+ });
+ }
+ }
+
+ //
+ // FNA3D_GenDepthStencilRenderbuffer
+ //
+
+ public static IntPtr FNA3D_GenDepthStencilRenderbuffer(IntPtr device,
+ int width, int height,
+ DepthFormat format,
+ int multiSampleCount)
+ {
+ if (multiSampleCount > 0)
+ {
+ // no support for multisampling; see FNA3D_SetRenderTargets
+ throw new PlatformNotSupportedException();
+ }
+
+ int bufferId = 0;
+
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ var state = (State) renderer.UserData;
+
+ var id = new int[1];
+ GLES20.glGenRenderbuffers(1, id, 0);
+ if (id[0] != 0)
+ {
+ GLES20.glBindRenderbuffer(GLES20.GL_RENDERBUFFER, id[0]);
+ GLES20.glRenderbufferStorage(GLES20.GL_RENDERBUFFER,
+ DepthFormatToDepthStorage[(int) format],
+ width, height);
+
+ bufferId = id[0];
+ }
+ });
+
+ return (IntPtr) bufferId;
+ }
+
+ //
+ // Delete Render Target
+ //
+
+ public static void FNA3D_AddDisposeRenderbuffer(IntPtr device, IntPtr renderbuffer)
+ {
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ GLES20.glDeleteRenderbuffers(1, new int[] { (int) renderbuffer }, 0);
+ });
+ }
+
+ //
+ // IsRenderToTexture
+ //
+
+ public static bool IsRenderToTexture(GraphicsDevice graphicsDevice)
+ {
+ // should be called in the renderer thread context
+ return ((State) Renderer.Get(graphicsDevice.GLDevice).UserData).RenderToTexture;
+ }
+
+ //
+ // DepthFormatToDepthStorage
+ //
+
+ static int[] DepthFormatToDepthStorage = new int[]
+ {
+ GLES20.GL_ZERO, // invalid
+ GLES20.GL_DEPTH_COMPONENT16, // DepthFormat.Depth16
+ GLES30.GL_DEPTH_COMPONENT24, // DepthFormat.Depth24
+ GLES30.GL_DEPTH24_STENCIL8, // DepthFormat.Depth24Stencil8
+ };
+
+ //
+ // FNA3D_RenderTargetBinding
+ //
+
+ public struct FNA3D_RenderTargetBinding
+ {
+ public byte type; // 0 for RenderTarget2D, 1 for RenderTargetCube
+ public int data1; // 2D width or cube size
+ public int data2; // 2D height or cube face
+ public int levelCount;
+ public int multiSampleCount;
+ public IntPtr texture;
+ public IntPtr colorBuffer;
+ }
+
+ //
+ // State
+ //
+
+ private partial class State
+ {
+ public bool RenderToTexture;
+ public int TargetFramebuffer;
+ public int ActiveAttachments;
+ }
+
+ }
+}
+
diff --git a/BNA/src/FNA3D_Tex.cs b/BNA/src/FNA3D_Tex.cs
new file mode 100644
index 0000000..4db1ca1
--- /dev/null
+++ b/BNA/src/FNA3D_Tex.cs
@@ -0,0 +1,577 @@
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+using android.opengl;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ public static partial class FNA3D
+ {
+
+ //
+ // FNA3D_SupportsDXT1
+ //
+
+ public static byte FNA3D_SupportsDXT1(IntPtr device)
+ {
+ var fmts = Renderer.Get(device).TextureFormats;
+ bool f = -1 != Array.IndexOf(fmts, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT);
+ return (byte) (f ? 1 : 0);
+ }
+
+ //
+ // FNA3D_SupportsS3TC
+ //
+
+ public static byte FNA3D_SupportsS3TC(IntPtr device)
+ {
+ var fmts = Renderer.Get(device).TextureFormats;
+ bool f = -1 != Array.IndexOf(fmts, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT)
+ && -1 != Array.IndexOf(fmts, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT);
+ return (byte) (f ? 1 : 0);
+ }
+
+ //
+ // Create Textures
+ //
+
+ private static int CreateTexture(Renderer renderer, int textureKind,
+ SurfaceFormat format, int levelCount)
+ {
+ int[] id = new int[1];
+ GLES20.glGenTextures(1, id, 0);
+ if (id[0] != 0)
+ {
+ GLES20.glBindTexture(textureKind, id[0]);
+
+ var state = (State) renderer.UserData;
+ state.TextureConfigs[id[0]] =
+ new int[] { textureKind, (int) format, levelCount };
+
+ }
+ return id[0];
+ }
+
+ public static IntPtr FNA3D_CreateTexture2D(IntPtr device, SurfaceFormat format,
+ int width, int height, int levelCount,
+ byte isRenderTarget)
+ {
+ var renderer = Renderer.Get(device);
+ if ( width <= 0 || width > renderer.TextureSize
+ || height <= 0 || height > renderer.TextureSize)
+ {
+ throw new ArgumentException($"bad texture size {width} x {height}");
+ }
+
+ int textureFormat = SurfaceFormatToTextureFormat[(int) format];
+ int internalFormat = SurfaceFormatToTextureInternalFormat[(int) format];
+ int dataType, dataSize;
+
+ if (textureFormat == GLES20.GL_COMPRESSED_TEXTURE_FORMATS)
+ {
+ dataType = textureFormat;
+ dataSize = SurfaceFormatToTextureDataSize[(int) format];
+ }
+ else
+ {
+ dataType = SurfaceFormatToTextureDataType[(int) format];
+ dataSize = 0;
+ }
+
+ if ( textureFormat == GLES20.GL_ZERO
+ || internalFormat == GLES20.GL_ZERO
+ || dataType == GLES20.GL_ZERO)
+ {
+ throw new PlatformNotSupportedException(
+ $"unsupported texture format {format}");
+ }
+
+ int textureId = 0;
+ renderer.Send( () =>
+ {
+ int id = CreateTexture(renderer, GLES20.GL_TEXTURE_2D, format, levelCount);
+ if (id != 0)
+ {
+ textureId = id;
+
+ for (int level = 0; level < levelCount; level++)
+ {
+ if (textureFormat == GLES20.GL_COMPRESSED_TEXTURE_FORMATS)
+ {
+ int levelSize = dataSize *
+ ((width + 3) / 4) * ((height + 3) / 4);
+
+ GLES20.glCompressedTexImage2D(GLES20.GL_TEXTURE_2D,
+ level, internalFormat,
+ width, height, /* border */ 0,
+ levelSize, null);
+ }
+ else
+ {
+ GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,
+ level, internalFormat,
+ width, height, /* border */ 0,
+ textureFormat, dataType, null);
+ }
+
+ width >>= 1;
+ height >>= 1;
+ if (width <= 0)
+ width = 1;
+ if (height <= 0)
+ height = 1;
+ }
+ }
+ });
+ return (IntPtr) textureId;
+ }
+
+
+
+ //
+ // Delete Textures
+ //
+
+ public static void FNA3D_AddDisposeTexture(IntPtr device, IntPtr texture)
+ {
+ var renderer = Renderer.Get(device);
+ renderer.Send( () =>
+ {
+ GLES20.glDeleteTextures(1, new int[] { (int) texture }, 0);
+
+ var state = (State) renderer.UserData;
+ state.TextureConfigs.Remove((int) texture);
+ });
+ }
+
+
+
+ //
+ // Set Texture Data
+ //
+
+ private static void SetTextureData(Renderer renderer, int textureId,
+ int x, int y, int w, int h, int level,
+ object dataObject, int dataOffset, int dataLength)
+ {
+ java.nio.Buffer buffer = dataObject switch
+ {
+ sbyte[] byteArray =>
+ java.nio.ByteBuffer.wrap(byteArray, dataOffset, dataLength),
+
+ int[] intArray =>
+ java.nio.IntBuffer.wrap(intArray, dataOffset / 4, dataLength / 4),
+
+ _ => throw new ArgumentException(dataObject?.GetType().ToString()),
+ };
+
+ renderer.Send( () =>
+ {
+ GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
+
+ var state = (State) renderer.UserData;
+ var config = state.TextureConfigs[textureId];
+ int format = config[1];
+
+ int textureFormat = SurfaceFormatToTextureFormat[format];
+ if (textureFormat == GLES20.GL_COMPRESSED_TEXTURE_FORMATS)
+ {
+ int internalFormat = SurfaceFormatToTextureInternalFormat[format];
+
+ GLES20.glCompressedTexSubImage2D(GLES20.GL_TEXTURE_2D,
+ level, x, y, w, h, internalFormat, dataLength, buffer);
+ }
+ else
+ {
+ int dataType = SurfaceFormatToTextureDataType[format];
+ int dataSize = SurfaceFormatToTextureDataSize[format];
+
+ if (dataSize != 4)
+ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, dataSize);
+
+ GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, level, x, y, w, h,
+ textureFormat, dataType, buffer);
+
+ if (dataSize != 4)
+ GLES20.glPixelStorei(GLES20.GL_UNPACK_ALIGNMENT, 4);
+
+ }
+ });
+ }
+
+ public static void FNA3D_SetTextureData2D(IntPtr device, IntPtr texture,
+ int x, int y, int w, int h, int level,
+ IntPtr data, int dataLength)
+ {
+ // FNA Texture2D uses GCHandle::Alloc and GCHandle::AddrOfPinnedObject.
+ // we use GCHandle::FromIntPtr to convert that address to an object reference.
+ // see also: system.runtime.interopservices.GCHandle struct in baselib.
+ int dataOffset = (int) data;
+ var dataObject = System.Runtime.InteropServices.GCHandle.FromIntPtr(data).Target;
+ SetTextureData(Renderer.Get(device), (int) texture,
+ x, y, w, h, level, dataObject, dataOffset, dataLength);
+ }
+
+
+
+ //
+ // ReadImageStream
+ //
+
+ public static IntPtr ReadImageStream(System.IO.Stream stream,
+ out int width, out int height, out int len,
+ int forceWidth, int forceHeight, bool zoom)
+ {
+ var bitmap = LoadBitmap(stream);
+
+ int[] pixels;
+ if (forceWidth == -1 || forceHeight == -1)
+ {
+ pixels = GetPixels(bitmap, out width, out height, out len);
+ }
+ else
+ {
+ pixels = CropAndScale(bitmap, forceWidth, forceHeight, zoom,
+ out width, out height, out len);
+ }
+
+ // keep a strong reference until FNA3D_Image_Free is called
+ ImagePixels.set(pixels);
+
+ return System.Runtime.InteropServices.GCHandle.Alloc(
+ pixels, System.Runtime.InteropServices.GCHandleType.Pinned)
+ .AddrOfPinnedObject();
+
+
+ android.graphics.Bitmap LoadBitmap(System.IO.Stream stream)
+ {
+ if (stream is Microsoft.Xna.Framework.TitleContainer.TitleStream titleStream)
+ {
+ var bitmap = android.graphics.BitmapFactory
+ .decodeStream(titleStream.JavaStream);
+ if ( bitmap == null
+ || bitmap.getConfig() != android.graphics.Bitmap.Config.ARGB_8888)
+ {
+ string reason = (bitmap == null) ? "unspecified error"
+ : $"unsupported config '{bitmap.getConfig()}'";
+ throw new System.BadImageFormatException(
+ $"Load failed for bitmap image '{titleStream.Name}': {reason}");
+ }
+
+ return bitmap;
+ }
+
+ throw new ArgumentException(stream?.GetType()?.ToString());
+ }
+
+
+ int[] CropAndScale(android.graphics.Bitmap bitmap,
+ int newWidth, int newHeight, bool zoom,
+ out int width, out int height, out int len)
+ {
+ int oldWidth = bitmap.getWidth();
+ int oldHeight = bitmap.getHeight();
+ bool scaleWidth = zoom ? (oldWidth < oldHeight) : (oldWidth > oldHeight);
+ float scaleFactor = scaleWidth ? ((float) newWidth / (float) oldWidth)
+ : ((float) newHeight / (float) oldHeight);
+ if (zoom)
+ {
+ int x, y, w, h;
+ if (scaleWidth)
+ {
+ x = 0;
+ y = (int) (oldHeight / 2 - (newHeight / scaleFactor) / 2);
+ w = oldWidth;
+ h = (int) (newHeight / scaleFactor);
+ }
+ else
+ {
+ x = (int) (oldWidth / 2 - (newWidth / scaleFactor) / 2);
+ y = 0;
+ w = (int) (newWidth / scaleFactor);
+ h = oldHeight;
+ }
+ bitmap = android.graphics.Bitmap.createBitmap(bitmap, x, y, w, h);
+ }
+ else
+ {
+ newWidth = (int) (oldWidth * scaleFactor);
+ newHeight = (int) (oldHeight * scaleFactor);
+ }
+
+ return GetPixels(android.graphics.Bitmap.createScaledBitmap(
+ bitmap, newWidth, newHeight, false),
+ out width, out height, out len);
+ }
+
+
+ int[] GetPixels(android.graphics.Bitmap bitmap,
+ out int width, out int height, out int len)
+ {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ var pixels = new int[w * h];
+ bitmap.copyPixelsToBuffer(java.nio.IntBuffer.wrap(pixels));
+ width = w;
+ height = h;
+ len = w * h * 4;
+ return pixels;
+ }
+ }
+
+ //
+ // FNA3D_Image_Free
+ //
+
+ public static void FNA3D_Image_Free(IntPtr mem)
+ {
+ // FNA calls this method after uploading the data returned by
+ // ReadImageStream, so we can safely discard the reference
+ ImagePixels.set(null);
+ }
+
+ //
+ // FNA3D_VerifySampler
+ //
+
+ public static void FNA3D_VerifySampler(IntPtr device, int index, IntPtr texture,
+ ref FNA3D_SamplerState sampler)
+ {
+ var samplerCopy = sampler;
+ var renderer = Renderer.Get(device);
+
+ renderer.Send( () =>
+ {
+ var state = (State) renderer.UserData;
+ var config = state.TextureConfigs[(int) texture];
+
+ GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + index);
+ GLES20.glBindTexture(config[0], (int) texture);
+
+ GLES20.glTexParameteri(config[0], GLES30.GL_TEXTURE_MAX_LEVEL,
+ config[2] - 1);
+ GLES20.glTexParameteri(config[0], GLES30.GL_TEXTURE_BASE_LEVEL,
+ samplerCopy.maxMipLevel);
+
+ GLES20.glTexParameteri(config[0], GLES20.GL_TEXTURE_WRAP_S,
+ TextureWrapMode[(int) samplerCopy.addressU]);
+ GLES20.glTexParameteri(config[0], GLES20.GL_TEXTURE_WRAP_T,
+ TextureWrapMode[(int) samplerCopy.addressV]);
+ if (config[0] == GLES30.GL_TEXTURE_3D)
+ {
+ GLES20.glTexParameteri(config[0], GLES30.GL_TEXTURE_WRAP_R,
+ TextureWrapMode[(int) samplerCopy.addressW]);
+ }
+
+ int magIndex = (int) samplerCopy.filter * 3;
+ int minIndex = magIndex + (config[2] <= 1 ? 1 : 2);
+
+ GLES20.glTexParameteri(config[0], GLES20.GL_TEXTURE_MAG_FILTER,
+ TextureFilterMode[magIndex]);
+ GLES20.glTexParameteri(config[0], GLES20.GL_TEXTURE_MIN_FILTER,
+ TextureFilterMode[minIndex]);
+
+ });
+ }
+
+
+
+ //
+ // SurfaceFormatToTextureFormat
+ //
+
+ static int[] SurfaceFormatToTextureFormat = new int[]
+ {
+ GLES20.GL_RGBA, // SurfaceFormat.Color
+ GLES20.GL_RGB, // SurfaceFormat.Bgr565
+ GLES20.GL_ZERO, // was GL_BGRA // SurfaceFormat.Bgra5551
+ GLES20.GL_ZERO, // was GL_BGRA // SurfaceFormat.Bgra4444
+ GLES20.GL_COMPRESSED_TEXTURE_FORMATS, // SurfaceFormat.Dxt1
+ GLES20.GL_COMPRESSED_TEXTURE_FORMATS, // SurfaceFormat.Dxt3
+ GLES20.GL_COMPRESSED_TEXTURE_FORMATS, // SurfaceFormat.Dxt5
+ GLES30.GL_RG, // SurfaceFormat.NormalizedByte2
+ GLES20.GL_RGBA, // SurfaceFormat.NormalizedByte4
+ GLES20.GL_RGBA, // SurfaceFormat.Rgba1010102
+ GLES30.GL_RG, // SurfaceFormat.Rg32
+ GLES20.GL_RGBA, // SurfaceFormat.Rgba64
+ GLES20.GL_ALPHA, // SurfaceFormat.Alpha8
+ GLES30.GL_RED, // SurfaceFormat.Single
+ GLES30.GL_RG, // SurfaceFormat.Vector2
+ GLES20.GL_RGBA, // SurfaceFormat.Vector4
+ GLES30.GL_RED, // SurfaceFormat.HalfSingle
+ GLES30.GL_RG, // SurfaceFormat.HalfVector2
+ GLES20.GL_RGBA, // SurfaceFormat.HalfVector4
+ GLES20.GL_RGBA, // SurfaceFormat.HdrBlendable
+ GLES20.GL_ZERO, // was GL_BGRA // SurfaceFormat.ColorBgraEXT
+ };
+
+ //
+ // SurfaceFormatToTextureInternalFormat
+ //
+
+ static int[] SurfaceFormatToTextureInternalFormat = new int[]
+ {
+ GLES30.GL_RGBA8, // SurfaceFormat.Color
+ GLES30.GL_RGB8, // SurfaceFormat.Bgr565
+ GLES20.GL_RGB5_A1, // SurfaceFormat.Bgra5551
+ GLES20.GL_RGBA4, // SurfaceFormat.Bgra4444
+ GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, // SurfaceFormat.Dxt1
+ GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, // SurfaceFormat.Dxt3
+ GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, // SurfaceFormat.Dxt5
+ GLES30.GL_RG8, // SurfaceFormat.NormalizedByte2
+ GLES30.GL_RGBA8, // SurfaceFormat.NormalizedByte4
+ GLES30.GL_RGB10_A2, // was ..._A2_EXT // SurfaceFormat.Rgba1010102
+ GLES20.GL_ZERO, // was GL_RG16 // SurfaceFormat.Rg32
+ GLES20.GL_ZERO, // was GL_RGBA16, // SurfaceFormat.Rgba64
+ GLES20.GL_ALPHA, // SurfaceFormat.Alpha8
+ GLES30.GL_R32F, // SurfaceFormat.Single
+ GLES30.GL_RG32F, // SurfaceFormat.Vector2
+ GLES30.GL_RGBA32F, // SurfaceFormat.Vector4
+ GLES30.GL_R16F, // SurfaceFormat.HalfSingle
+ GLES30.GL_RG16F, // SurfaceFormat.HalfVector2
+ GLES30.GL_RGBA16F, // SurfaceFormat.HalfVector4
+ GLES30.GL_RGBA16F, // SurfaceFormat.HdrBlendable
+ GLES30.GL_RGBA8, // SurfaceFormat.ColorBgraEXT
+ };
+
+ //
+ // SurfaceFormatToTextureDataType
+ //
+
+ static int[] SurfaceFormatToTextureDataType = new int[]
+ {
+ GLES20.GL_UNSIGNED_BYTE, // SurfaceFormat.Color
+ GLES20.GL_UNSIGNED_SHORT_5_6_5, // SurfaceFormat.Bgr565
+ GLES20.GL_ZERO, // was ..._5_5_5_1_REV // SurfaceFormat.Bgra5551
+ GLES20.GL_ZERO, // was ..._4_4_4_4_REV // SurfaceFormat.Bgra4444
+ GLES20.GL_ZERO, // not applicable // SurfaceFormat.Dxt1
+ GLES20.GL_ZERO, // not applicable // SurfaceFormat.Dxt3
+ GLES20.GL_ZERO, // not applicable // SurfaceFormat.Dxt5
+ GLES20.GL_BYTE, // SurfaceFormat.NormalizedByte2
+ GLES20.GL_BYTE, // SurfaceFormat.NormalizedByte4
+ GLES30.GL_UNSIGNED_INT_2_10_10_10_REV, // SurfaceFormat.Rgba1010102
+ GLES20.GL_UNSIGNED_SHORT, // SurfaceFormat.Rg32
+ GLES20.GL_UNSIGNED_SHORT, // SurfaceFormat.Rgba64
+ GLES20.GL_UNSIGNED_BYTE, // SurfaceFormat.Alpha8
+ GLES20.GL_FLOAT, // SurfaceFormat.Single
+ GLES20.GL_FLOAT, // SurfaceFormat.Vector2
+ GLES20.GL_FLOAT, // SurfaceFormat.Vector4
+ GLES30.GL_HALF_FLOAT, // SurfaceFormat.HalfSingle
+ GLES30.GL_HALF_FLOAT, // SurfaceFormat.HalfVector2
+ GLES30.GL_HALF_FLOAT, // SurfaceFormat.HalfVector4
+ GLES30.GL_HALF_FLOAT, // SurfaceFormat.HdrBlendable
+ GLES20.GL_UNSIGNED_BYTE // SurfaceFormat.ColorBgraEXT
+ };
+
+ // from ubiquitous extension EXT_texture_compression_s3tc
+ const int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1;
+ const int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2;
+ const int GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3;
+
+ //
+ // SurfaceFormatToTextureDataSize
+ //
+
+ static int[] SurfaceFormatToTextureDataSize = new int[]
+ {
+ 4, // SurfaceFormat.Color
+ 2, // SurfaceFormat.Bgr565
+ 2, // SurfaceFormat.Bgra5551
+ 2, // SurfaceFormat.Bgra4444
+ 8, // SurfaceFormat.Dxt1
+ 16, // SurfaceFormat.Dxt3
+ 16, // SurfaceFormat.Dxt5
+ 2, // SurfaceFormat.NormalizedByte2
+ 4, // SurfaceFormat.NormalizedByte4
+ 4, // SurfaceFormat.Rgba1010102
+ 4, // SurfaceFormat.Rg32
+ 8, // SurfaceFormat.Rgba64
+ 1, // SurfaceFormat.Alpha8
+ 4, // SurfaceFormat.Single
+ 8, // SurfaceFormat.Vector2
+ 16, // SurfaceFormat.Vector4
+ 2, // SurfaceFormat.HalfSingle
+ 4, // SurfaceFormat.HalfVector2
+ 8, // SurfaceFormat.HalfVector4
+ 8, // SurfaceFormat.HdrBlendable
+ 4, // SurfaceFormat.ColorBgraEXT
+ };
+
+ //
+ // TextureWrapMode
+ //
+
+ static int[] TextureWrapMode = new int[]
+ {
+ GLES20.GL_REPEAT, // TextureAddressMode.Wrap
+ GLES20.GL_CLAMP_TO_EDGE, // TextureAddressMode.Clamp
+ GLES20.GL_MIRRORED_REPEAT // TextureAddressMode.Mirror
+ };
+
+ //
+ // TextureFilterMode
+ //
+
+ static int[] TextureFilterMode = new int[]
+ {
+ // TextureFilter.Linear: mag filter, min filter, mipmap filter
+ GLES20.GL_LINEAR, GLES20.GL_LINEAR, GLES20.GL_LINEAR_MIPMAP_LINEAR,
+ // TextureFilter.Point
+ GLES20.GL_NEAREST, GLES20.GL_NEAREST, GLES20.GL_NEAREST_MIPMAP_NEAREST,
+ // TextureFilter.Anisotropic
+ GLES20.GL_LINEAR, GLES20.GL_LINEAR, GLES20.GL_LINEAR_MIPMAP_LINEAR,
+ // TextureFilter.LinearMipPoint
+ GLES20.GL_LINEAR, GLES20.GL_LINEAR, GLES20.GL_LINEAR_MIPMAP_NEAREST,
+ // TextureFilter.PointMipLinear
+ GLES20.GL_NEAREST, GLES20.GL_NEAREST, GLES20.GL_NEAREST_MIPMAP_LINEAR,
+ // TextureFilter.MinLinearMagPointMipLinear
+ GLES20.GL_NEAREST, GLES20.GL_LINEAR, GLES20.GL_LINEAR_MIPMAP_LINEAR,
+ // TextureFilter.MinLinearMagPointMipPoint
+ GLES20.GL_NEAREST, GLES20.GL_LINEAR, GLES20.GL_LINEAR_MIPMAP_NEAREST,
+ // TextureFilter.MinPointMagLinearMipLinear
+ GLES20.GL_LINEAR, GLES20.GL_NEAREST, GLES20.GL_NEAREST_MIPMAP_LINEAR,
+ // TextureFilter.MinPointMagLinearMipPoint
+ GLES20.GL_LINEAR, GLES20.GL_NEAREST, GLES20.GL_NEAREST_MIPMAP_NEAREST,
+ };
+
+ //
+ // FNA3D_SamplerState
+ //
+
+ public struct FNA3D_SamplerState
+ {
+ public TextureFilter filter;
+ public TextureAddressMode addressU;
+ public TextureAddressMode addressV;
+ public TextureAddressMode addressW;
+ public float mipMapLevelOfDetailBias;
+ public int maxAnisotropy;
+ public int maxMipLevel;
+ }
+
+
+
+ //
+ // data
+ //
+
+ private static readonly java.lang.ThreadLocal ImagePixels = new java.lang.ThreadLocal();
+
+ //
+ // state
+ //
+
+ private partial class State
+ {
+ // texture config array:
+ // #0 - target type
+ // #1 - SurfaceFormat
+ // #2 - levels count
+ public Dictionary TextureConfigs = new Dictionary();
+ }
+
+ }
+}
diff --git a/BNA/src/FNAPlatform.cs b/BNA/src/FNAPlatform.cs
new file mode 100644
index 0000000..6abd671
--- /dev/null
+++ b/BNA/src/FNAPlatform.cs
@@ -0,0 +1,172 @@
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Input.Touch;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework
+{
+
+ public static class FNAPlatform
+ {
+ //
+ // CreateWindow
+ //
+
+ private static GameWindow CreateWindowImpl() => GameRunner.Singleton;
+
+ public delegate GameWindow CreateWindowFunc();
+ public static readonly CreateWindowFunc CreateWindow = CreateWindowImpl;
+
+ //
+ // DisposeWindow
+ //
+
+ private static void DisposeWindowImpl(GameWindow window) { }
+
+ public delegate void DisposeWindowFunc(GameWindow window);
+ public static readonly DisposeWindowFunc DisposeWindow = DisposeWindowImpl;
+
+ //
+ // SupportsOrientationChanges
+ //
+
+ private static bool SupportsOrientationChangesImpl() => true;
+
+ public delegate bool SupportsOrientationChangesFunc();
+ public static readonly SupportsOrientationChangesFunc SupportsOrientationChanges = SupportsOrientationChangesImpl;
+
+ //
+ // GetGraphicsAdapters
+ //
+
+ private static GraphicsAdapter[] GetGraphicsAdaptersImpl()
+ {
+ var bounds = GameRunner.Singleton.ClientBounds;
+ var modesList = new List();
+ var theMode = new DisplayMode(bounds.Width, bounds.Height, SurfaceFormat.Color);
+ modesList.Add(theMode);
+ var modesCollection = new DisplayModeCollection(modesList);
+ var name = "Android Surface";
+ var theAdapter = new GraphicsAdapter(modesCollection, name, name);
+ return new GraphicsAdapter[] { (GraphicsAdapter) (object) theAdapter };
+ }
+
+ public delegate GraphicsAdapter[] GetGraphicsAdaptersFunc();
+ public static readonly GetGraphicsAdaptersFunc GetGraphicsAdapters = GetGraphicsAdaptersImpl;
+
+ //
+ // RegisterGame
+ //
+
+ public static GraphicsAdapter RegisterGameImpl(Game game) => GraphicsAdapter.Adapters[0];
+
+ public delegate GraphicsAdapter RegisterGameFunc(Game game);
+ public static readonly RegisterGameFunc RegisterGame = RegisterGameImpl;
+
+ //
+ // GetTouchCapabilities
+ //
+
+ public static TouchPanelCapabilities GetTouchCapabilitiesImpl()
+ => (TouchPanelCapabilities) (object) new TouchPanelCapabilities(true, 4);
+
+ public delegate TouchPanelCapabilities GetTouchCapabilitiesFunc();
+ public static readonly GetTouchCapabilitiesFunc GetTouchCapabilities = GetTouchCapabilitiesImpl;
+
+ //
+ // NeedsPlatformMainLoop
+ //
+
+ public static bool NeedsPlatformMainLoopImpl() => true;
+
+ public delegate bool NeedsPlatformMainLoopFunc();
+ public static readonly NeedsPlatformMainLoopFunc NeedsPlatformMainLoop = NeedsPlatformMainLoopImpl;
+
+ //
+ //
+ //
+
+ public static void RunPlatformMainLoopImpl(Game game)
+ => GameRunner.Singleton.MainLoop(game);
+
+ public delegate void RunPlatformMainLoopFunc(Game game);
+ public static readonly RunPlatformMainLoopFunc RunPlatformMainLoop = RunPlatformMainLoopImpl;
+
+ //
+ // UnregisterGame
+ //
+
+ public static void UnregisterGameImpl(Game game) { }
+
+ public delegate void UnregisterGameFunc(Game game);
+ public static readonly UnregisterGameFunc UnregisterGame = UnregisterGameImpl;
+
+ //
+ // OnIsMouseVisibleChanged
+ //
+
+ public static void OnIsMouseVisibleChangedImpl(bool visible) { }
+
+ public delegate void OnIsMouseVisibleChangedFunc(bool visible);
+ public static readonly OnIsMouseVisibleChangedFunc OnIsMouseVisibleChanged = OnIsMouseVisibleChangedImpl;
+
+ //
+ // GetNumTouchFingers
+ //
+
+ public static int GetNumTouchFingersImpl() => Mouse.NumTouchFingers.get();
+
+ public delegate int GetNumTouchFingersFunc();
+ public static readonly GetNumTouchFingersFunc GetNumTouchFingers = GetNumTouchFingersImpl;
+
+ //
+ // GetStorageRoot
+ //
+
+ public static string GetStorageRootImpl()
+ {
+ var s = GameRunner.Singleton.Activity.getFilesDir();
+ return s.getAbsolutePath();
+ }
+
+ public delegate string GetStorageRootFunc();
+ public static readonly GetStorageRootFunc GetStorageRoot = GetStorageRootImpl;
+
+ //
+ // GetDriveInfo
+ //
+
+ public static System.IO.DriveInfo GetDriveInfoImpl(string storageRoot) => null;
+
+ public delegate System.IO.DriveInfo GetDriveInfoFunc(string storageRoot);
+ public static readonly GetDriveInfoFunc GetDriveInfo = GetDriveInfoImpl;
+
+ //
+ // GetMouseState
+ //
+
+ /*public static void GetMouseStateImpl(IntPtr window, out int x, out int y, out ButtonState left, out ButtonState middle, out ButtonState right, out ButtonState x1, out ButtonState x2)
+ {
+ x = 0;
+ y = 0;
+ left = ButtonState.Released;
+ middle = ButtonState.Released;
+ right = ButtonState.Released;
+ x1 = ButtonState.Released;
+ x2 = ButtonState.Released;
+ }
+
+ public delegate void GetMouseStateFunc(IntPtr window, out int x, out int y, out ButtonState left, out ButtonState middle, out ButtonState right, out ButtonState x1, out ButtonState x2);
+ public static readonly GetMouseStateFunc GetMouseState = GetMouseStateImpl;*/
+
+ //
+ // TextInputCharacters
+ //
+
+ public static readonly char[] TextInputCharacters = new char[0];
+ }
+
+}
diff --git a/BNA/src/GameRunner.cs b/BNA/src/GameRunner.cs
new file mode 100644
index 0000000..5a78b48
--- /dev/null
+++ b/BNA/src/GameRunner.cs
@@ -0,0 +1,413 @@
+
+using System;
+using Microsoft.Xna.Framework.Graphics;
+using GL10 = javax.microedition.khronos.opengles.GL10;
+using EGLConfig = javax.microedition.khronos.egl.EGLConfig;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework
+{
+
+ public class GameRunner : GameWindow, IServiceProvider, java.lang.Runnable
+
+ {
+
+ private Activity activity;
+ private System.Collections.Hashtable dict;
+ private int clientWidth, clientHeight;
+ private Rectangle clientBounds;
+ private bool recreateActivity;
+
+ private java.util.concurrent.atomic.AtomicInteger inModal;
+ private java.util.concurrent.atomic.AtomicInteger shouldPause;
+ private java.util.concurrent.atomic.AtomicInteger shouldResume;
+ private java.util.concurrent.atomic.AtomicInteger shouldExit;
+ private java.util.concurrent.atomic.AtomicInteger shouldEvents;
+ private android.os.ConditionVariable waitForPause;
+ private android.os.ConditionVariable waitForResume;
+
+ private static readonly java.lang.ThreadLocal selfTls =
+ new java.lang.ThreadLocal();
+
+ private const int CONFIG_EVENT = 1;
+ private const int TOUCH_EVENT = 2;
+
+ //
+ // constructor
+ //
+
+ public GameRunner(Activity activity)
+ {
+ this.activity = activity;
+
+ UpdateConfiguration(false);
+
+ inModal = new java.util.concurrent.atomic.AtomicInteger();
+ shouldPause = new java.util.concurrent.atomic.AtomicInteger();
+ shouldResume = new java.util.concurrent.atomic.AtomicInteger();
+ shouldExit = new java.util.concurrent.atomic.AtomicInteger();
+ shouldEvents = new java.util.concurrent.atomic.AtomicInteger();
+ waitForPause = new android.os.ConditionVariable();
+ waitForResume = new android.os.ConditionVariable();
+ }
+
+ //
+ // Singleton and Activity properties
+ //
+
+ public static GameRunner Singleton
+ => (GameRunner) selfTls.get()
+ ?? throw new System.InvalidOperationException("not main thread");
+
+ public android.app.Activity Activity => activity;
+
+ //
+ // InModal
+ //
+
+ public bool InModal
+ {
+ get => inModal.get() != 0 ? true : false;
+ set => inModal.set(value ? 1 : 0);
+ }
+
+ //
+ // Thread run() method
+ //
+
+ [java.attr.RetainName]
+ public void run()
+ {
+ selfTls.set(this);
+
+ RunMainMethod();
+
+ shouldExit.set(1);
+ waitForPause.open();
+
+ activity.FinishAndRestart(recreateActivity);
+ }
+
+ //
+ // RunMainMethod
+ //
+
+ private void RunMainMethod()
+ {
+ try
+ {
+ CallMainMethod(GetMainClass());
+ }
+ catch (Exception e)
+ {
+ GameRunner.Log("========================================");
+ GameRunner.Log(e.ToString());
+ GameRunner.Log("========================================");
+ System.Windows.Forms.MessageBox.Show(e.ToString());
+ }
+
+
+ Type GetMainClass()
+ {
+ Type clsType = null;
+
+ var clsName = activity.GetMetaAttr("main.class", true);
+ if (clsName != null)
+ {
+ if (clsName[0] == '.')
+ clsName = activity.getPackageName() + clsName;
+
+ clsType = System.Type.GetType(clsName, false, true);
+ }
+
+ if (clsType == null)
+ {
+ throw new Exception($"main class '{clsName}' not found");
+ }
+
+ return clsType;
+ }
+
+
+ void CallMainMethod(Type mainClass)
+ {
+ var method = mainClass.GetMethod("Main");
+ if (method.IsStatic)
+ {
+ method.Invoke(null, new object[method.GetParameters().Length]);
+ }
+ else
+ {
+ throw new Exception($"missing or invalid method 'Main' in type '{mainClass}'");
+ }
+ }
+ }
+
+ //
+ // MainLoop
+ //
+
+ public void MainLoop(Game game)
+ {
+ int pauseCount = 0;
+
+ while (game.RunApplication)
+ {
+
+ //
+ // pause game if required
+ //
+
+ if (shouldPause.get() != pauseCount)
+ {
+ pauseCount = shouldPause.incrementAndGet();
+
+ // FNA.Game calls game.OnDeactivated()
+ game.IsActive = false;
+
+ if (shouldExit.get() != 0)
+ break;
+
+ PauseGame(false);
+
+ shouldResume.incrementAndGet();
+ waitForPause.open();
+ waitForResume.block();
+ waitForResume.close();
+
+ if (! ResumeGame(false))
+ break;
+
+ // on resume from pause, reset input state
+ Microsoft.Xna.Framework.Input.Mouse.WindowHandle =
+ Microsoft.Xna.Framework.Input.Mouse.WindowHandle;
+
+ // FNA.Game calls game.OnActivated()
+ game.IsActive = true;
+ }
+
+ //
+ // handle various events as indicated
+ //
+
+ int eventBits = shouldEvents.get();
+ if (eventBits != 0)
+ {
+ while (! shouldEvents.compareAndSet(eventBits, 0))
+ eventBits = shouldEvents.get();
+
+ if ((eventBits & CONFIG_EVENT) != 0)
+ UpdateConfiguration(true);
+
+ if ((eventBits & TOUCH_EVENT) != 0)
+ {
+ Microsoft.Xna.Framework.Input.Mouse
+ .HandleEvents(clientWidth, clientHeight);
+ }
+ }
+
+ //
+ // run one game frame
+ //
+
+ game.Tick();
+ }
+
+ InModal = true;
+ game.RunApplication = false;
+ }
+
+ //
+ // PauseGame
+ //
+
+ public void PauseGame(bool enterModal)
+ {
+ Renderer.Pause(activity);
+ if (enterModal)
+ InModal = true;
+ }
+
+ //
+ // ResumeGame
+ //
+
+ public bool ResumeGame(bool leaveModal)
+ {
+ if (leaveModal)
+ InModal = false;
+
+ if (shouldExit.get() != 0)
+ return false;
+
+ if (! Renderer.CanResume(activity))
+ {
+ // restart because we lost the GL context and state
+ recreateActivity = true;
+
+ // in case we are called from MessageBox, make sure
+ // the main loop sees that we need to exit.
+ shouldExit.set(1);
+ shouldPause.incrementAndGet();
+
+ return false;
+ }
+
+ return true;
+ }
+
+ //
+ // Callbacks from Android activity UI thread:
+ // onPause, onResume, onDestroy, onTouchEvent
+ //
+
+ public void ActivityPause()
+ {
+ if (! InModal)
+ {
+ shouldPause.incrementAndGet();
+ waitForPause.block();
+ if (shouldExit.get() == 0)
+ waitForPause.close();
+ }
+ }
+
+ //
+ // ActivityResume
+ //
+
+ public void ActivityResume()
+ {
+ if (shouldResume.compareAndSet(1, 0))
+ waitForResume.open();
+ }
+
+ //
+ // ActivityDestroy
+ //
+
+ public void ActivityDestroy()
+ {
+ shouldExit.set(1);
+ ActivityResume();
+ ActivityPause();
+ }
+
+ //
+ // ActivityTouch
+ //
+
+ public void ActivityTouch(android.view.MotionEvent motionEvent)
+ {
+ Microsoft.Xna.Framework.Input.Mouse.QueueEvent(motionEvent);
+ for (;;)
+ {
+ int v = shouldEvents.get();
+ if (shouldEvents.compareAndSet(v, v | TOUCH_EVENT))
+ break;
+ }
+ }
+
+ //
+ // OnSurfaceChanged
+ //
+
+ public void OnSurfaceChanged()
+ {
+ for (;;)
+ {
+ int v = shouldEvents.get();
+ if (shouldEvents.compareAndSet(v, v | CONFIG_EVENT))
+ break;
+ }
+ }
+
+ //
+ // UpdateConfiguration
+ //
+
+ void UpdateConfiguration(bool withCallback)
+ {
+ var metrics = new android.util.DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
+
+ clientWidth = metrics.widthPixels;
+ clientHeight = metrics.heightPixels;
+ clientBounds = new Rectangle(0, 0, clientWidth, clientHeight);
+
+ if (dict == null)
+ dict = new System.Collections.Hashtable();
+
+ // int dpi - pixels per inch
+ dict["dpi"] = (int) ((metrics.xdpi + metrics.ydpi) * 0.5f);
+
+ if (withCallback)
+ {
+ OnClientSizeChanged();
+ OnOrientationChanged();
+ }
+ }
+
+ //
+ // GetService
+ //
+
+ public object GetService(Type type)
+ {
+ if (object.ReferenceEquals(type, typeof(System.Collections.IDictionary)))
+ return dict.Clone();
+ return null;
+ }
+
+ //
+ // GameWindow interface
+ //
+
+ public override Rectangle ClientBounds => clientBounds;
+
+ public override string ScreenDeviceName => "Android";
+
+ public override bool AllowUserResizing { get => false; set { } }
+
+ public override void SetSupportedOrientations(DisplayOrientation orientations)
+ => CurrentOrientation = orientations;
+
+ public override DisplayOrientation CurrentOrientation
+ {
+ get => (clientWidth < clientHeight) ? DisplayOrientation.Portrait
+ : DisplayOrientation.LandscapeLeft;
+ set
+ {
+ bool portrait = 0 != (value & DisplayOrientation.Portrait);
+ bool landscape = 0 != (value & ( DisplayOrientation.LandscapeLeft
+ | DisplayOrientation.LandscapeRight));
+ int r;
+ if (portrait && (! landscape))
+ r = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
+ else if (landscape && (! portrait))
+ r = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
+ else
+ r = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
+ activity.setRequestedOrientation(r);
+ }
+ }
+
+ public static void Log(string s) => Microsoft.Xna.Framework.Activity.Log(s);
+
+ //
+ // not implemented
+ //
+
+ public override IntPtr Handle => IntPtr.Zero;
+
+ public override void SetTitle(string title) { }
+
+ public override void BeginScreenDeviceChange(bool willBeFullScreen) { }
+
+ public override void EndScreenDeviceChange(string screenDeviceName,
+ int clientWidth, int clientHeight) { }
+
+
+ }
+
+}
diff --git a/BNA/src/Import.cs b/BNA/src/Import.cs
new file mode 100644
index 0000000..64aac7e
--- /dev/null
+++ b/BNA/src/Import.cs
@@ -0,0 +1,228 @@
+
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using Microsoft.Xna.Framework.Graphics;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework
+{
+
+ //
+ // GameWindow
+ //
+
+ [java.attr.Discard] // discard in output
+ public abstract class GameWindow
+ {
+ public abstract IntPtr Handle { get; }
+ public abstract bool AllowUserResizing { get; set; }
+ public abstract Rectangle ClientBounds { get; }
+ public abstract string ScreenDeviceName { get; }
+ public abstract void SetSupportedOrientations(DisplayOrientation orientations);
+ public abstract DisplayOrientation CurrentOrientation { get; set; }
+ public abstract void BeginScreenDeviceChange(bool willBeFullScreen);
+ public abstract void EndScreenDeviceChange(string screenDeviceName, int clientWidth, int clientHeight);
+ public abstract void SetTitle(string title);
+ protected void OnActivated() { }
+ protected void OnDeactivated() { }
+ protected void OnPaint() { }
+ protected void OnScreenDeviceNameChanged() { }
+ protected void OnClientSizeChanged() { }
+ protected void OnOrientationChanged() { }
+ public static readonly int DefaultClientWidth = 800;
+ public static readonly int DefaultClientHeight = 600;
+ }
+
+ //
+ // Game
+ //
+
+ [java.attr.Discard] // discard in output
+ public abstract class Game
+ {
+ public bool IsActive { get; set; }
+ public bool RunApplication;
+ public abstract void Tick();
+ }
+}
+
+
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ //
+ // GraphicsDevice
+ //
+
+ [java.attr.Discard] // discard in output
+ public class GraphicsDevice
+ {
+ public readonly IntPtr GLDevice;
+ public TextureCollection Textures { get; }
+ }
+
+ //
+ // DisplayMode
+ //
+
+ [java.attr.Discard] // discard in output
+ public class DisplayMode
+ {
+ public DisplayMode(int width, int height, SurfaceFormat format) { }
+ }
+
+ //
+ // DisplayModeCollection
+ //
+
+ [java.attr.Discard] // discard in output
+ public class DisplayModeCollection
+ {
+ public DisplayModeCollection(List setmodes) { }
+ }
+
+ //
+ // GraphicsAdapter
+ //
+
+ [java.attr.Discard] // discard in output
+ public sealed class GraphicsAdapter
+ {
+ public GraphicsAdapter(DisplayModeCollection modes, string name, string description) { }
+ public static ReadOnlyCollection Adapters => null;
+ }
+
+ //
+ // GraphicsResource
+ //
+
+ [java.attr.Discard] // discard in output
+ public abstract class GraphicsResource
+ {
+ public GraphicsDevice GraphicsDevice { get; set; }
+ protected virtual void Dispose(bool disposing) { }
+ public virtual bool IsDisposed => false;
+ }
+
+ //
+ // SpriteBatch
+ //
+
+ [java.attr.Discard] // discard in output
+ public class SpriteBatch
+ {
+ public struct VertexPositionColorTexture4
+ {
+ public Vector3 Position0;
+ public Color Color0;
+ public Vector2 TextureCoordinate0;
+
+ public Vector3 Position1;
+ public Color Color1;
+ public Vector2 TextureCoordinate1;
+
+ public Vector3 Position2;
+ public Color Color2;
+ public Vector2 TextureCoordinate2;
+
+ public Vector3 Position3;
+ public Color Color3;
+ public Vector2 TextureCoordinate3;
+ }
+ }
+
+ //
+ // EffectParameterCollection
+ //
+
+ [java.attr.Discard] // discard in output
+ public class EffectParameterCollection
+ {
+ public EffectParameterCollection(List value) { }
+ public EffectParameter this[int index] => null;
+ public int Count { get; }
+ }
+
+ //
+ // EffectPass
+ //
+
+ [java.attr.Discard] // discard in output
+ public sealed class EffectPass
+ {
+ public EffectPass(string name, EffectAnnotationCollection annotations,
+ Effect parent, IntPtr technique, uint passIndex) { }
+ }
+
+ //
+ // EffectPassCollection
+ //
+
+ [java.attr.Discard] // discard in output
+ public class EffectPassCollection
+ {
+ public EffectPassCollection(List value) { }
+ }
+
+ //
+ // EffectTechnique
+ //
+
+ [java.attr.Discard] // discard in output
+ public sealed class EffectTechnique
+ {
+ public EffectTechnique(string name, IntPtr pointer, EffectPassCollection passes,
+ EffectAnnotationCollection annotations) { }
+ public string Name { get; }
+ }
+
+ //
+ // EffectTechniqueCollection
+ //
+
+ [java.attr.Discard] // discard in output
+ public class EffectTechniqueCollection
+ {
+ public EffectTechniqueCollection(List value) { }
+ }
+
+}
+
+
+
+namespace Microsoft.Xna.Framework.Input
+{
+ [java.attr.Discard] // discard in output
+ public struct MouseState
+ {
+ public int X { get; set; }
+ public int Y { get; set; }
+ public ButtonState LeftButton { get; set; }
+ }
+}
+
+
+
+namespace Microsoft.Xna.Framework.Input.Touch
+{
+
+ //
+ // TouchPanelCapabilities
+ //
+
+ [java.attr.Discard] // discard in output
+ public struct TouchPanelCapabilities
+ {
+ public TouchPanelCapabilities(bool isConnected, int maximumTouchCount) { }
+ }
+
+ [java.attr.Discard] // discard in output
+ public class TouchPanel
+ {
+ public static void INTERNAL_onTouchEvent(int fingerId, TouchLocationState state,
+ float x, float y, float dx, float dy) { }
+ }
+
+}
diff --git a/BNA/src/MessageBox.cs b/BNA/src/MessageBox.cs
new file mode 100644
index 0000000..fe167ac
--- /dev/null
+++ b/BNA/src/MessageBox.cs
@@ -0,0 +1,73 @@
+
+using System;
+using Microsoft.Xna.Framework;
+
+namespace System.Windows.Forms
+{
+
+ public enum DialogResult
+ {
+ None, OK, Cancel, Abort, Retry, Ignore, Yes, No
+ }
+
+ public class MessageBox
+ {
+
+ public static volatile bool Showing;
+ public static volatile bool Disable;
+
+ public static DialogResult Show(string text)
+ {
+ if (! Disable)
+ {
+ var gameRunner = GameRunner.Singleton;
+ var activity = gameRunner.Activity;
+ if ( gameRunner.InModal || activity == null
+ || android.os.Looper.getMainLooper().getThread()
+ == java.lang.Thread.currentThread())
+ {
+ GameRunner.Log(text);
+ }
+ else
+ {
+ gameRunner.PauseGame(true);
+
+ var waitObj = new android.os.ConditionVariable();
+ Show(activity, text, (_) => waitObj.open());
+ waitObj.block();
+
+ gameRunner.ResumeGame(true);
+
+ }
+ }
+ return DialogResult.OK;
+ }
+
+ static void Show(android.app.Activity activity, string text,
+ System.Action onClick)
+ {
+ activity.runOnUiThread(((java.lang.Runnable.Delegate) ( () => {
+
+ var dlg = new android.app.AlertDialog.Builder(activity);
+ dlg.setPositiveButton((java.lang.CharSequence) (object) "Close",
+ ((android.content.DialogInterface.OnClickListener.Delegate)
+ ((dialog, which) =>
+ { Showing = false; onClick(DialogResult.Yes); }
+ )).AsInterface());
+ dlg.setOnDismissListener(
+ ((android.content.DialogInterface.OnDismissListener.Delegate)
+ ((dialog) =>
+ { Showing = false; onClick(DialogResult.Cancel); }
+ )).AsInterface());
+ dlg.create();
+ dlg.setMessage((java.lang.CharSequence) (object) text);
+
+ Showing = true;
+ dlg.show();
+
+ })).AsInterface());
+ }
+
+ }
+
+}
diff --git a/BNA/src/Mouse.cs b/BNA/src/Mouse.cs
new file mode 100644
index 0000000..6537e6b
--- /dev/null
+++ b/BNA/src/Mouse.cs
@@ -0,0 +1,188 @@
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Input.Touch;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework.Input
+{
+
+ public static class Mouse
+ {
+
+ private static MouseState state;
+
+ private static Queue motionEvents = new Queue();
+
+ private static List<(int id, float x, float y)> fingerXYs =
+ new List<(int id, float x, float y)>();
+
+ public static java.util.concurrent.atomic.AtomicInteger NumTouchFingers =
+ new java.util.concurrent.atomic.AtomicInteger();
+
+
+
+ public static IntPtr WindowHandle
+ {
+ get => IntPtr.Zero;
+ set
+ {
+ state = default(MouseState);
+ lock (motionEvents)
+ {
+ motionEvents.Clear();
+ }
+ fingerXYs.Clear();
+ NumTouchFingers.set(0);
+ }
+ }
+ public static int INTERNAL_BackBufferWidth;
+ public static int INTERNAL_BackBufferHeight;
+
+
+
+ public static void QueueEvent(android.view.MotionEvent motionEvent)
+ {
+ lock (motionEvents)
+ {
+ motionEvents.Enqueue(
+ android.view.MotionEvent.obtainNoHistory(motionEvent));
+ }
+ }
+
+ public static void HandleEvents(int clientWidth, int clientHeight)
+ {
+ for (;;)
+ {
+ android.view.MotionEvent motionEvent;
+ lock (motionEvents)
+ {
+ if (motionEvents.Count == 0)
+ motionEvent = null;
+ else
+ {
+ motionEvent =
+ (android.view.MotionEvent) motionEvents.Dequeue();
+ }
+ }
+
+ if (motionEvent == null)
+ return;
+
+ HandleOneEvent(motionEvent, clientWidth, clientHeight);
+ }
+ }
+
+
+
+ private static void HandleOneEvent(android.view.MotionEvent motionEvent,
+ int clientWidth, int clientHeight)
+ {
+ int action = motionEvent.getActionMasked();
+
+ if ( action == android.view.MotionEvent.ACTION_DOWN
+ || action == android.view.MotionEvent.ACTION_MOVE
+ || action == android.view.MotionEvent.ACTION_POINTER_DOWN)
+ {
+ state.LeftButton = ButtonState.Pressed;
+ state.X = (int) Clamp(motionEvent.getX(),
+ clientWidth, INTERNAL_BackBufferWidth);
+ state.Y = (int) Clamp(motionEvent.getY(),
+ clientHeight, INTERNAL_BackBufferHeight);
+
+ var which = (action == android.view.MotionEvent.ACTION_DOWN)
+ ? TouchLocationState.Pressed
+ : TouchLocationState.Moved;
+
+ SendTouchEvents(motionEvent, which, clientWidth, clientHeight);
+ }
+
+ else if ( action == android.view.MotionEvent.ACTION_UP
+ || action == android.view.MotionEvent.ACTION_POINTER_UP
+ || action == android.view.MotionEvent.ACTION_CANCEL)
+ {
+ state.LeftButton = ButtonState.Released;
+
+ SendTouchEvents(motionEvent, TouchLocationState.Released,
+ clientWidth, clientHeight);
+ }
+ }
+
+ private static void SendTouchEvents(android.view.MotionEvent motionEvent,
+ TouchLocationState whichTouchEvent,
+ int clientWidth, int clientHeight)
+ {
+ int pointerCount = motionEvent.getPointerCount();
+ var eventArray =
+ new (int id, float x, float y, float dx, float dy)[pointerCount];
+
+ for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++)
+ {
+ int id = motionEvent.getPointerId(pointerIndex);
+ var x = Clamp(motionEvent.getX(pointerIndex), clientWidth, 1);
+ var y = Clamp(motionEvent.getY(pointerIndex), clientHeight, 1);
+ float dx, dy;
+
+ int fingerCount = fingerXYs.Count;
+ int fingerIndex = 0;
+ while (fingerIndex < fingerCount)
+ {
+ if (fingerXYs[fingerIndex].id == id)
+ break;
+ fingerIndex++;
+ }
+
+ if (fingerIndex == fingerCount)
+ {
+ dx = dy = 0f;
+ if (whichTouchEvent != TouchLocationState.Released)
+ {
+ fingerXYs.Add((id: id, x: x, y: y));
+ }
+ }
+ else
+ {
+ dx = x - fingerXYs[fingerIndex].x;
+ dy = y - fingerXYs[fingerIndex].y;
+
+ if (whichTouchEvent != TouchLocationState.Released)
+ {
+ fingerXYs[fingerIndex] = (id: id, x: x, y: y);
+ }
+ else
+ {
+ fingerXYs.RemoveAt(fingerIndex);
+ }
+ }
+
+ eventArray[pointerIndex] = (id: id, x: x, y: y, dx: dx, dy: dy);
+ }
+
+ NumTouchFingers.set(fingerXYs.Count);
+
+ for (int eventIndex = 0; eventIndex < pointerCount; eventIndex++)
+ {
+ var e = eventArray[eventIndex];
+ TouchPanel.INTERNAL_onTouchEvent(e.id, whichTouchEvent, e.x, e.y, e.dx, e.dy);
+ }
+ }
+
+ private static float Clamp(float v, int clientSize, int backbufferSize)
+ {
+ if (v < 0)
+ v = 0;
+ if (v > clientSize)
+ v = clientSize;
+ return v * backbufferSize / clientSize;
+ }
+
+
+
+ public static MouseState GetState() => state;
+
+ public static void SetPosition(int x, int y) { }
+
+ }
+
+}
diff --git a/BNA/src/Renderer.cs b/BNA/src/Renderer.cs
new file mode 100644
index 0000000..e0083f3
--- /dev/null
+++ b/BNA/src/Renderer.cs
@@ -0,0 +1,337 @@
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using android.opengl;
+using GL10 = javax.microedition.khronos.opengles.GL10;
+using EGLConfig = javax.microedition.khronos.egl.EGLConfig;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+
+ public class Renderer : android.opengl.GLSurfaceView.Renderer
+ {
+
+ //
+ // renderer data
+ //
+
+ private android.opengl.GLSurfaceView surface;
+ private android.os.ConditionVariable waitObject;
+ private java.util.concurrent.atomic.AtomicInteger paused;
+ private Action actionOnChanged;
+
+ public object UserData;
+
+ //
+ // surface configuration
+ //
+
+ public int SurfaceWidth, SurfaceHeight;
+ public DepthFormat SurfaceDepthFormat;
+ public int TextureUnits;
+ public int TextureSize;
+ public int[] TextureFormats;
+
+ //
+ // constructor
+ //
+
+ private Renderer(android.app.Activity activity, Action onChanged,
+ int redSize, int greenSize, int blueSize,
+ int alphaSize, int depthSize, int stencilSize)
+ {
+ waitObject = new android.os.ConditionVariable();
+ paused = new java.util.concurrent.atomic.AtomicInteger();
+ actionOnChanged = onChanged;
+
+ activity.runOnUiThread(((java.lang.Runnable.Delegate) (() =>
+ {
+ surface = new android.opengl.GLSurfaceView(activity);
+ surface.setEGLContextClientVersion(3); // OpenGL ES 3.0
+ surface.setEGLConfigChooser(redSize, greenSize, blueSize,
+ alphaSize, depthSize, stencilSize);
+ surface.setPreserveEGLContextOnPause(true);
+ surface.setRenderer(this);
+ surface.setRenderMode(android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+ activity.setContentView(surface);
+
+ })).AsInterface());
+
+ // wait for one onDrawFrame callback, which tells us that
+ // GLSurfaceView finished initializing the GL context
+ if (! waitObject.block(8000))
+ throw new NoSuitableGraphicsDeviceException("cannot create GLSurfaceView");
+
+ var clientBounds = GameRunner.Singleton.ClientBounds;
+ if (SurfaceWidth != clientBounds.Width || SurfaceHeight != clientBounds.Height)
+ {
+ // while not common, it is possible for the screen to rotate,
+ // between the time the Window/GameRunner is created, and the
+ // time the renderer is created. we want to identify this.
+ if (actionOnChanged != null)
+ actionOnChanged();
+ }
+ }
+
+ //
+ // Send
+ //
+
+ public void Send(Action action)
+ {
+ Exception exc = null;
+ if (paused.get() == 0)
+ {
+ var cond = new android.os.ConditionVariable();
+ surface.queueEvent(((java.lang.Runnable.Delegate) (() =>
+ {
+ var error = GLES20.glGetError();
+ if (error == GLES20.GL_NO_ERROR)
+ {
+ try
+ {
+ action();
+ }
+ catch (Exception exc2)
+ {
+ exc = exc2;
+ }
+ error = GLES20.glGetError();
+ }
+ if (error != GLES20.GL_NO_ERROR)
+ exc = new Exception($"GL Error {error}");
+ cond.open();
+ })).AsInterface());
+ cond.block();
+ }
+ if (exc != null)
+ {
+ throw new AggregateException(exc.Message, exc);
+ }
+ }
+
+ //
+ // Present
+ //
+
+ public void Present()
+ {
+ waitObject.close();
+ surface.requestRender();
+ waitObject.block();
+ }
+
+ //
+ // Renderer interface
+ //
+
+ [java.attr.RetainName]
+ public void onSurfaceCreated(GL10 unused, EGLConfig config)
+ {
+ // if onSurfaceCreated is called while resuming from pause,
+ // it means the GL context was lost
+ paused.compareAndSet(1, -1);
+ }
+
+ [java.attr.RetainName]
+ public void onSurfaceChanged(GL10 unused, int width, int height)
+ {
+ bool changed = (SurfaceWidth != width || SurfaceHeight != height)
+ && (SurfaceWidth != 0 || SurfaceHeight != 0);
+
+ SurfaceWidth = width;
+ SurfaceHeight = height;
+ InitConfig();
+
+ if (changed && actionOnChanged != null)
+ actionOnChanged();
+ }
+
+ [java.attr.RetainName]
+ public void onDrawFrame(GL10 unused) => waitObject.open();
+
+ //
+ // InitConfig
+ //
+
+ private void InitConfig()
+ {
+ var data = new int[5];
+ GLES20.glGetIntegerv(GLES20.GL_DEPTH_BITS, data, 0);
+ GLES20.glGetIntegerv(GLES20.GL_STENCIL_BITS, data, 1);
+ GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_IMAGE_UNITS, data, 2);
+ GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, data, 3);
+ GLES20.glGetIntegerv(GLES20.GL_NUM_COMPRESSED_TEXTURE_FORMATS, data, 4);
+
+ if (data[0] /* DEPTH_BITS */ >= 24)
+ {
+ SurfaceDepthFormat = (data[1] /* STENCIL_BITS */ >= 8)
+ ? DepthFormat.Depth24Stencil8
+ : DepthFormat.Depth24;
+ }
+ else if (data[0] /* DEPTH_BITS */ >= 16)
+ SurfaceDepthFormat = DepthFormat.Depth16;
+ else
+ SurfaceDepthFormat = DepthFormat.None;
+
+ TextureUnits = data[2]; // GL_MAX_TEXTURE_IMAGE_UNITS
+ TextureSize = data[3]; // GL_MAX_TEXTURE_SIZE
+
+ TextureFormats = new int[data[4]]; // GL_NUM_COMPRESSED_TEXTURE_FORMATS
+ GLES20.glGetIntegerv(GLES20.GL_COMPRESSED_TEXTURE_FORMATS, TextureFormats, 0);
+ }
+
+ //
+ // Create
+ //
+
+ public static IntPtr Create(android.app.Activity activity, Action onChanged,
+ int redSize, int greenSize, int blueSize,
+ int alphaSize, int depthSize, int stencilSize)
+ {
+ for (;;)
+ {
+ lock (RendererObjects)
+ {
+ var deviceId = java.lang.System.nanoTime();
+
+ foreach (var oldRendererObject in RendererObjects)
+ {
+ if (oldRendererObject.deviceId == deviceId)
+ {
+ deviceId = 0;
+ break;
+ }
+ }
+
+ if (deviceId == 0)
+ {
+ java.lang.Thread.sleep(1);
+ continue;
+ }
+
+ RendererObjects.Insert(0, new RendererObject()
+ {
+ deviceId = deviceId,
+ renderer = new Renderer(activity, onChanged,
+ redSize, greenSize, blueSize,
+ alphaSize, depthSize, stencilSize),
+ activity = new java.lang.@ref.WeakReference(activity),
+ });
+
+ return (IntPtr) deviceId;
+ }
+ }
+ }
+
+ //
+ // GetRenderer
+ //
+
+ public static Renderer Get(IntPtr deviceId)
+ {
+ var longDeviceId = (long) deviceId;
+ lock (RendererObjects)
+ {
+ foreach (var renderer in RendererObjects)
+ {
+ if (renderer.deviceId == longDeviceId)
+ return renderer.renderer;
+ }
+ }
+ throw new ArgumentException("invalid device ID");
+ }
+
+ //
+ // Release
+ //
+
+ public void Release()
+ {
+ lock (RendererObjects)
+ {
+ for (int i = RendererObjects.Count; i-- > 0; )
+ {
+ if (RendererObjects[i].renderer == this)
+ {
+ RendererObjects[i].renderer.surface = null;
+ RendererObjects[i].renderer = null;
+ RendererObjects.RemoveAt(i);
+ }
+ }
+ }
+ }
+
+ //
+ // GetRenderersForActivity
+ //
+
+ private static List GetRenderersForActivity(android.app.Activity activity)
+ {
+ var list = new List();
+ lock (RendererObjects)
+ {
+ foreach (var renderer in RendererObjects)
+ {
+ if (renderer.activity.get() == activity)
+ list.Add(renderer.renderer);
+ }
+ }
+ return list;
+ }
+
+ //
+ // Pause
+ //
+
+ public static void Pause(android.app.Activity activity)
+ {
+ foreach (var renderer in GetRenderersForActivity(activity))
+ {
+ renderer.surface.onPause();
+ renderer.paused.set(1);
+ }
+ }
+
+ //
+ // CanResume
+ //
+
+ public static bool CanResume(android.app.Activity activity)
+ {
+ foreach (var renderer in GetRenderersForActivity(activity))
+ {
+ if (renderer.paused.get() != 0)
+ {
+ renderer.waitObject.close();
+ renderer.surface.onResume();
+ renderer.waitObject.block();
+
+ if (! renderer.paused.compareAndSet(1, 0))
+ {
+ // cannot resume because we lost the GL context,
+ // see also PauseRenderers and onSurfaceCreated
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ //
+ // data
+ //
+
+ private static List RendererObjects = new List();
+
+ private class RendererObject
+ {
+ public long deviceId;
+ public Renderer renderer;
+ public java.lang.@ref.WeakReference activity;
+ }
+
+ }
+
+}
diff --git a/BNA/src/Resources.cs b/BNA/src/Resources.cs
new file mode 100644
index 0000000..22902e4
--- /dev/null
+++ b/BNA/src/Resources.cs
@@ -0,0 +1,188 @@
+
+using System;
+
+namespace Microsoft.Xna.Framework.Graphics
+{
+ public class Resources
+ {
+
+ //
+ // SpriteEffect
+ //
+
+ public static byte[] SpriteEffect => System.Text.Encoding.ASCII.GetBytes(@"
+
+#technique SpriteBatch
+
+--- vertex ---
+
+layout (location = 0) in vec3 pos;
+layout (location = 1) in vec4 color;
+layout (location = 2) in vec2 uv;
+out vec4 f_color;
+out vec2 f_uv;
+uniform mat4 MatrixTransform;
+uniform float MultiplierY;
+
+void main()
+{
+ gl_Position = MatrixTransform * vec4(pos, 1.0);
+ gl_Position.y *= MultiplierY;
+ f_color = color;
+ f_uv = uv;
+}
+
+--- fragment ---
+
+in vec4 f_color;
+in vec2 f_uv;
+out vec4 o_color;
+uniform sampler2D image;
+
+void main()
+{
+ o_color = texture(image, f_uv) * f_color;
+}
+
+--- end ---
+");
+
+ //
+ // BasicEffect
+ //
+
+ public static byte[] BasicEffect => System.Text.Encoding.ASCII.GetBytes(@"
+
+#technique BasicEffect
+
+--- vertex ---
+
+layout (location = 0) in vec3 pos;
+layout (location = 1) in vec3 norm;
+layout (location = 2) in vec2 uv;
+
+uniform int ShaderIndex;
+uniform float MultiplierY;
+
+uniform mat4 WorldViewProj;
+uniform mat4 World;
+uniform mat3 WorldInverseTranspose;
+
+uniform vec4 DiffuseColor;
+uniform vec4 FogVector;
+
+out vec3 f_Position;
+out vec3 f_Normal;
+out vec2 f_TexCoord;
+out float f_FogFactor;
+out vec2 f_uv;
+
+void main()
+{
+ vec4 pos4 = vec4(pos, 1.0);
+
+ f_FogFactor = clamp(dot(pos4, FogVector), 0.0, 1.0);
+
+ f_Position = vec3(World * pos4);
+ f_Normal = normalize(WorldInverseTranspose * norm);
+
+ gl_Position = WorldViewProj * pos4;
+ gl_Position.y *= MultiplierY;
+ f_TexCoord = uv;
+}
+
+--- fragment ---
+
+in vec3 f_Position;
+in vec3 f_Normal;
+in vec2 f_TexCoord;
+in float f_FogFactor;
+
+uniform vec3 DirLight0Direction;
+uniform vec3 DirLight0DiffuseColor;
+uniform vec3 DirLight0SpecularColor;
+
+uniform vec3 DirLight1Direction;
+uniform vec3 DirLight1DiffuseColor;
+uniform vec3 DirLight1SpecularColor;
+
+uniform vec3 DirLight2Direction;
+uniform vec3 DirLight2DiffuseColor;
+uniform vec3 DirLight2SpecularColor;
+
+uniform vec4 DiffuseColor;
+uniform vec3 EmissiveColor;
+uniform vec3 SpecularColor;
+uniform float SpecularPower;
+uniform vec3 FogColor;
+
+uniform vec3 EyePosition;
+
+uniform sampler2D Texture;
+uniform int ShaderIndex;
+
+out vec4 o_color;
+
+void ComputeOneLight(vec3 eyeVector, vec3 worldNormal, out vec3 o_diffuse, out vec3 o_specular)
+{
+ float dotL = dot(worldNormal, -DirLight0Direction);
+ float dotH = dot(worldNormal, normalize(eyeVector - DirLight0Direction));
+
+ float zeroL = step(0.0, dotL);
+
+ float diffuse = zeroL * dotL;
+ float specular = pow(max(dotH, 0.0) * zeroL, SpecularPower);
+
+ o_diffuse = (DirLight0DiffuseColor * diffuse) * DiffuseColor.rgb + EmissiveColor;
+ o_specular = (DirLight0SpecularColor * specular) * SpecularColor;
+}
+
+void ComputeThreeLights(vec3 eyeVector, vec3 worldNormal, out vec3 o_diffuse, out vec3 o_specular)
+{
+ mat3 lightDirections = mat3(DirLight0Direction, DirLight1Direction, DirLight2Direction);
+ mat3 lightDiffuse = mat3(DirLight0DiffuseColor, DirLight1DiffuseColor, DirLight2DiffuseColor);
+ mat3 lightSpecular = mat3(DirLight0SpecularColor, DirLight1SpecularColor, DirLight2SpecularColor);
+ mat3 halfVectors = mat3(normalize(eyeVector - DirLight0Direction),
+ normalize(eyeVector - DirLight1Direction),
+ normalize(eyeVector - DirLight2Direction));
+
+ vec3 dotL = worldNormal * -lightDirections;
+ vec3 dotH = worldNormal * halfVectors;
+
+ vec3 zeroL = step(vec3(0.0), dotL);
+
+ vec3 diffuse = zeroL * dotL;
+ vec3 specular = pow(max(dotH, vec3(0.0)) * zeroL, vec3(SpecularPower));
+
+ o_diffuse = (lightDiffuse * diffuse) * DiffuseColor.rgb + EmissiveColor;
+ o_specular = (lightSpecular * specular) * SpecularColor;
+}
+
+void main()
+{
+ vec3 eyeVector = normalize(EyePosition - f_Position);
+ vec3 worldNormal = normalize(f_Normal);
+ vec3 diffuse, specular;
+ if ((ShaderIndex & 24) == 24)
+ ComputeThreeLights(eyeVector, worldNormal, diffuse, specular);
+ else if ((ShaderIndex & 24) != 0)
+ ComputeOneLight(eyeVector, worldNormal, diffuse, specular);
+ else
+ {
+ diffuse = DiffuseColor.rgb;
+ specular = vec3(0.0);
+ }
+
+ vec4 color = mix(vec4(1.0), texture(Texture, f_TexCoord), bvec4(ShaderIndex & 4));
+ color.rgb *= diffuse;
+ color.rgb += specular * color.a;
+ color.rgb = mix(color.rgb, FogColor * color.a, f_FogFactor);
+ color.a *= DiffuseColor.a;
+ o_color = color;
+}
+
+--- end ---
+");
+
+ }
+}
diff --git a/BNA/src/TitleContainer.cs b/BNA/src/TitleContainer.cs
new file mode 100644
index 0000000..20e438e
--- /dev/null
+++ b/BNA/src/TitleContainer.cs
@@ -0,0 +1,62 @@
+
+using System;
+using System.IO;
+using Microsoft.Xna.Framework.Graphics;
+#pragma warning disable 0436
+
+namespace Microsoft.Xna.Framework
+{
+
+ internal static class TitleContainer
+ {
+
+ public static Stream OpenStream(string name)
+ {
+ var stream = GameRunner.Singleton.Activity
+ .getAssets().open(name.Replace('\\', '/'));
+ if (stream == null)
+ throw new System.IO.FileNotFoundException(name);
+ return new TitleStream(stream, name);
+ }
+
+ public class TitleStream : Stream
+ {
+ public java.io.InputStream JavaStream;
+ public string Name;
+
+ public TitleStream(java.io.InputStream javaStream, string name)
+ {
+ JavaStream = javaStream;
+ Name = name;
+ }
+
+ public override bool CanRead => true;
+ public override bool CanWrite => false;
+ public override bool CanSeek => false;
+
+ public override int Read(byte[] buffer, int offset, int count)
+ => JavaStream.read((sbyte[]) (object) buffer, offset, count);
+
+ //
+ // unused methods and properties
+ //
+
+ public override long Length => throw new System.PlatformNotSupportedException();
+ public override long Position
+ {
+ get => throw new System.PlatformNotSupportedException();
+ set => throw new System.PlatformNotSupportedException();
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ => throw new System.PlatformNotSupportedException();
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ => throw new System.PlatformNotSupportedException();
+ public override void SetLength(long value)
+ => throw new System.PlatformNotSupportedException();
+ public override void Flush() => throw new System.PlatformNotSupportedException();
+ }
+
+ }
+
+}
diff --git a/Demo1/AndroidManifest.xml b/Demo1/AndroidManifest.xml
new file mode 100644
index 0000000..a1b15ab
--- /dev/null
+++ b/Demo1/AndroidManifest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Demo1/Demo1.sln b/Demo1/Demo1.sln
new file mode 100644
index 0000000..05fff15
--- /dev/null
+++ b/Demo1/Demo1.sln
@@ -0,0 +1,47 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30503.244
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo1", "Demo1\Demo1.csproj", "{223C1770-AB2F-4278-B8E3-349580A51644}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Demo1Content", "Demo1Content\Demo1Content.contentproj", "{E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}"
+EndProject
+Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Demo1FSharp", "Demo1FSharp\Demo1FSharp.fsproj", "{E3649229-B2DD-4CE8-AF10-7814CD306EA5}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Debug|Any CPU.Build.0 = Debug|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Debug|x86.ActiveCfg = Debug|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Debug|x86.Build.0 = Debug|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Release|Any CPU.ActiveCfg = Release|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Release|Any CPU.Build.0 = Release|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Release|x86.ActiveCfg = Release|x86
+ {223C1770-AB2F-4278-B8E3-349580A51644}.Release|x86.Build.0 = Release|x86
+ {E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}.Debug|x86.ActiveCfg = Debug|x86
+ {E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}.Release|Any CPU.ActiveCfg = Release|x86
+ {E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}.Release|x86.ActiveCfg = Release|x86
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Debug|x86.ActiveCfg = Debug|x86
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Debug|x86.Build.0 = Debug|x86
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Release|x86.ActiveCfg = Release|x86
+ {E3649229-B2DD-4CE8-AF10-7814CD306EA5}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {4E3915BB-BD1B-4790-A50A-63DE2AEB9DAE}
+ EndGlobalSection
+EndGlobal
diff --git a/Demo1/Demo1/Config.cs b/Demo1/Demo1/Config.cs
new file mode 100644
index 0000000..9a31ece
--- /dev/null
+++ b/Demo1/Demo1/Config.cs
@@ -0,0 +1,74 @@
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Demo1
+{
+ public static class Config
+ {
+
+ public static int ClientWidth;
+ public static int ClientHeight;
+ public static int PixelsPerInch;
+
+ public static void InitGraphics(Game game)
+ {
+ // by default, if the field DisplayOrientation is left as default,
+ // BNA will fit the game 'window' to the Android screen size.
+ // this may or not be appropriate. it may be preferrable to use
+ // a PreparingDeviceSettings hook, as shown below, to explicitly
+ // set the size and orientation.
+
+ new GraphicsDeviceManager(game).PreparingDeviceSettings += ((sender, args) =>
+ {
+ var pp = args.GraphicsDeviceInformation.PresentationParameters;
+ pp.BackBufferWidth = game.Window.ClientBounds.Width;
+ pp.BackBufferHeight = game.Window.ClientBounds.Height;
+ pp.DisplayOrientation = game.Window.CurrentOrientation;
+ pp.RenderTargetUsage = RenderTargetUsage.PreserveContents;
+ });
+ }
+
+ public static void InitWindow(GameWindow window)
+ {
+ // request notification as the 'window' changes orientation.
+ // if AndroidManifest.xml specifies a locked orientation,
+ // this may not be needed.
+ window.ClientSizeChanged += WindowResized;
+
+ // initialize the window configuration
+ WindowResized(window, null);
+ }
+
+ private static void WindowResized(object sender, EventArgs eventArgs)
+ {
+ if (sender is GameWindow window)
+ {
+ ClientWidth = window.ClientBounds.Width;
+ ClientHeight = window.ClientBounds.Height;
+
+ // the BNA GameWindow object (the sender parameter) provides
+ // an IDictionary object through an IServiceProvider interface.
+ // the dictionary can be used to query information that is not
+ // otherwise accessible via XNA interfaces. at this time, only
+ // the screen DPI (dots per inch) value is provided.
+
+ PixelsPerInch = 144;
+ if (((object) window) is IServiceProvider windowServiceProvider)
+ {
+ var windowDict = (System.Collections.IDictionary)
+ windowServiceProvider.GetService(
+ typeof(System.Collections.IDictionary));
+ if (windowDict != null)
+ {
+ PixelsPerInch = (int) windowDict["dpi"];
+ }
+ }
+ }
+ Console.WriteLine($">>> WINDOW CONFIG {ClientWidth} x {ClientHeight} @ {PixelsPerInch} ppi");
+ }
+
+ }
+
+}
diff --git a/Demo1/Demo1/CubeDemo.cs b/Demo1/Demo1/CubeDemo.cs
new file mode 100644
index 0000000..951d80a
--- /dev/null
+++ b/Demo1/Demo1/CubeDemo.cs
@@ -0,0 +1,184 @@
+
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input.Touch;
+
+namespace Demo1
+{
+
+ public class CubeDemo : DrawableGameComponent
+ {
+
+#if CUSTOM_VERTEX_BUFFER
+ // custom vertex buffers are not supported at this time
+ private VertexPositionNormalTextureColor[] cube;
+#else
+ private VertexPositionNormalTexture[] cube;
+#endif
+ private BasicEffect theEffect;
+ private float angle;
+ private int shaderCycler;
+
+
+ public CubeDemo(Game game) : base(game)
+ {
+ var face = new Vector3[]
+ {
+ //TopLeft-BottomLeft-TopRight
+ new Vector3(-1f, 1f, 0f), new Vector3(-1f, -1f, 0f), new Vector3( 1f, 1f, 0f),
+ //BottomLeft-BottomRight-TopRight
+ new Vector3(-1f, -1f, 0f), new Vector3( 1f, -1f, 0f), new Vector3( 1f, 1f, 0f),
+ };
+
+ var faceNormals = new Vector3[]
+ {
+ Vector3.UnitZ, -Vector3.UnitZ, //Front & Back faces
+ Vector3.UnitX, -Vector3.UnitX, //Left & Right faces
+ Vector3.UnitY, -Vector3.UnitY, //Top & Bottom faces
+ };
+
+ var ang90 = (float) Math.PI / 2f;
+ var faceRotations = new Matrix[]
+ {
+ Matrix.CreateRotationY(2f * ang90),
+ Matrix.CreateRotationY(0f),
+ Matrix.CreateRotationY(-ang90),
+ Matrix.CreateRotationY(ang90),
+ Matrix.CreateRotationX(ang90),
+ Matrix.CreateRotationX(-ang90)
+ };
+
+#if CUSTOM_VERTEX_BUFFER
+ cube = new VertexPositionNormalTextureColor[36];
+ for (int x = 0; x < cube.Length; x++)
+ {
+ var i = x % 6;
+ var j = x / 6;
+ cube[x] = new VertexPositionNormalTextureColor(
+ Vector3.Transform(face[i], faceRotations[j]) + faceNormals[j],
+ faceNormals[j], Vector2.Zero, Color.Red);
+ }
+#else
+ var uvCoords = new Vector2[] {
+ Vector2.Zero, Vector2.UnitY, Vector2.UnitX, Vector2.UnitY, Vector2.One, Vector2.UnitX };
+ cube = new VertexPositionNormalTexture[36];
+ for (int x = 0; x < cube.Length; x++)
+ {
+ var i = x % 6;
+ var j = x / 6;
+ cube[x] = new VertexPositionNormalTexture(
+ Vector3.Transform(face[i], faceRotations[j]) + faceNormals[j],
+ faceNormals[j], uvCoords[i] * 2f);
+ }
+#endif
+ }
+
+
+ public override void Initialize()
+ {
+ theEffect = new BasicEffect(Game.GraphicsDevice)
+ {
+#if CUSTOM_VERTEX_BUFFER
+ VertexColorEnabled = true,
+#endif
+ AmbientLightColor = new Vector3(0f, 0.2f, 0f),
+ LightingEnabled = true,
+ TextureEnabled = true,
+ PreferPerPixelLighting = true,
+ FogStart = -10f, FogEnd = 20f,
+ View = Matrix.CreateTranslation(0f, 0f, -10f),
+ };
+
+ theEffect.Texture = Game.Content.Load("4x4");
+
+ theEffect.DirectionalLight0.Enabled = true;
+ theEffect.DirectionalLight0.DiffuseColor = new Vector3(1f, 1f, 0f);
+ theEffect.DirectionalLight0.Direction = Vector3.Down;
+
+ theEffect.DirectionalLight1.Enabled = true;
+ theEffect.DirectionalLight1.DiffuseColor = new Vector3(0f, 1f, 0f);
+ theEffect.DirectionalLight1.SpecularColor = new Vector3(0f, 0f, 1f);
+ theEffect.DirectionalLight1.Direction = Vector3.Right;
+
+ theEffect.DirectionalLight2.Enabled = true;
+ theEffect.DirectionalLight2.DiffuseColor = new Vector3(1f, 0f, 0f);
+ theEffect.DirectionalLight2.SpecularColor = new Vector3(0f, 0f, 1f);
+ theEffect.DirectionalLight2.Direction = Vector3.Left;
+
+ shaderCycler = Storage.GetInt("CubeDemo_ShaderCycler", 1);
+ UpdateEffect();
+
+ angle = Storage.GetFloat("CubeDemo_Angle", 0f);
+
+ base.Initialize();
+ }
+
+
+
+ public override void Update(GameTime gameTime)
+ {
+ angle += 0.005f;
+ if (angle > 2f * (float) Math.PI)
+ angle = 0f;
+ var R = Matrix.CreateRotationY(angle) * Matrix.CreateRotationX(0.4f);
+ var T = Matrix.CreateTranslation(0f, 0f, 5f);
+ theEffect.World = R * T;
+
+ if (Touch.LastGesture.GestureType == Microsoft.Xna.Framework.Input.Touch.GestureType.FreeDrag)
+ {
+ angle += Touch.LastGesture.Delta.X * 0.01f;
+ Touch.LastGesture = default(GestureSample);
+ }
+
+ Storage.Set("CubeDemo_Angle", angle);
+ base.Update(gameTime);
+ }
+
+
+ public override void Draw(GameTime gameTime)
+ {
+ theEffect.Projection = Matrix.CreatePerspectiveFieldOfView(
+ (float)Math.PI / 4.0f,
+ (float)Config.ClientWidth / (float)Config.ClientHeight,
+ 1f, 10f);
+
+ GraphicsDevice.SamplerStates[0] = ((shaderCycler & 16) == 0) ? SamplerState.PointWrap
+ : SamplerState.LinearWrap;
+
+ Game.GraphicsDevice.RasterizerState = new RasterizerState();
+ foreach (var pass in theEffect.CurrentTechnique.Passes)
+ {
+ pass.Apply();
+ Game.GraphicsDevice.DrawUserPrimitives(
+ PrimitiveType.TriangleList, cube, 0, 12);
+ }
+
+ var shaderIndex = theEffect.Parameters["ShaderIndex"].GetValueInt32();
+ var rect = ((Game1) Game).DrawText(
+ $"SHADER INDEX {shaderIndex}\nTAP HERE TO CYCLE", 0.2f, 0.4f, 1.5f, 0.7f);
+
+ if (Touch.Clicked(rect))
+ {
+ Storage.Set("CubeDemo_ShaderCycler", ++shaderCycler);
+ UpdateEffect();
+ }
+
+ base.Draw(gameTime);
+ }
+
+
+ void UpdateEffect()
+ {
+ theEffect.FogEnabled = ((shaderCycler & 1) != 0) ? false : true;
+ theEffect.TextureEnabled = ((shaderCycler & 2) != 0) ? false : true;
+ theEffect.PreferPerPixelLighting = ((shaderCycler & 4) != 0) ? false : true;
+ theEffect.DirectionalLight1.Enabled = ((shaderCycler & 8) != 0) ? false : true;
+ theEffect.DirectionalLight2.Enabled = ((shaderCycler & 8) != 0) ? false : true;
+ theEffect.LightingEnabled = ((shaderCycler & 16) != 0) ? false : true;
+ }
+
+ }
+
+}
diff --git a/Demo1/Demo1/Demo1.csproj b/Demo1/Demo1/Demo1.csproj
new file mode 100644
index 0000000..2c947b0
--- /dev/null
+++ b/Demo1/Demo1/Demo1.csproj
@@ -0,0 +1,110 @@
+
+
+ {223C1770-AB2F-4278-B8E3-349580A51644}
+ {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Debug
+ x86
+ WinExe
+ Properties
+ Demo1
+ Demo1
+ v4.6.1
+
+
+ v4.0
+ Windows
+ HiDef
+ ed304386-2da8-41d9-bfb0-3d6469f2ace6
+ Game
+ Game.ico
+ GameThumbnail.png
+
+
+ true
+ full
+ false
+ ..\..\.obj\Demo1\Debug\
+ DEBUG;TRACE;WINDOWS
+ prompt
+ 4
+ true
+ false
+ x86
+ false
+ 8.0
+ MinimumRecommendedRules.ruleset
+
+
+ pdbonly
+ true
+ ..\..\.obj\Demo1\Release\
+ TRACE;WINDOWS
+ prompt
+ 4
+ true
+ false
+ x86
+ true
+ 8.0
+
+
+ $(OutputPath)\Intermediate
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ Demo1Content
+ Content
+
+
+ {e3649229-b2dd-4ce8-af10-7814cd306ea5}
+ Demo1FSharp
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo1/Demo1/Font.cs b/Demo1/Demo1/Font.cs
new file mode 100644
index 0000000..604ff1d
--- /dev/null
+++ b/Demo1/Demo1/Font.cs
@@ -0,0 +1,85 @@
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Demo1
+{
+
+ public class Font
+ {
+
+ private SpriteBatch spriteBatch;
+ private SpriteFont spriteFont;
+
+
+ public Font(Game game, SpriteBatch spriteBatch, string fontName)
+ {
+ spriteFont = game.Content.Load(fontName);
+ this.spriteBatch = spriteBatch;
+ }
+
+
+ public Rectangle Measure(Vector4 pos, Vector4 size, string text)
+ {
+ var mm = spriteFont.MeasureString(text);
+
+ var wh = new Vector2(size.X, size.Y) * Config.PixelsPerInch;
+ var xy = new Vector2(pos.X, pos.Y) * Config.PixelsPerInch;
+ xy.X += pos.Z * Config.ClientWidth - size.Z * wh.X;
+ xy.Y += pos.W * Config.ClientHeight - size.W * wh.Y;
+
+ return new Rectangle((int) xy.X, (int) xy.Y, (int) wh.X, (int) wh.Y);
+ }
+
+
+
+ public Rectangle Measure(Vector2 pos, Vector2 size, string text)
+ {
+ var pos4 = new Vector4(pos.X, pos.Y,
+ pos.X >= 0f ? 0f : 1f,
+ pos.Y >= 0f ? 0f : 1f);
+ var size4 = new Vector4(size.X, size.Y, 0f, 0f);
+ return Measure(pos4, size4, text);
+ }
+
+
+ public void Draw(Rectangle rect, Color color, string text)
+ {
+ var mm = spriteFont.MeasureString(text);
+
+ var xy = new Vector2(rect.Left, rect.Top);
+ var wh = new Vector2(rect.Width, rect.Height);
+ var scl = wh / mm;
+
+ spriteBatch.DrawString(spriteFont, text, xy, color, 0f, Vector2.Zero, scl,
+ SpriteEffects.None, 0f);
+ }
+
+
+ public void Draw(Vector4 pos, Vector4 size, Color color, string text)
+ {
+ var mm = spriteFont.MeasureString(text);
+
+ var wh = new Vector2(size.X, size.Y) * Config.PixelsPerInch;
+ var xy = new Vector2(pos.X, pos.Y) * Config.PixelsPerInch;
+ xy.X += pos.Z * Config.ClientWidth- size.Z * wh.X;
+ xy.Y += pos.W * Config.ClientHeight - size.W * wh.Y;
+
+ var scl = wh / mm;
+
+ spriteBatch.DrawString(spriteFont, text, xy, color, 0f, Vector2.Zero, scl,
+ SpriteEffects.None, 0f);
+ }
+
+ public void Draw(Vector2 pos, Vector2 size, Color color, string text)
+ {
+ var pos4 = new Vector4(pos.X, pos.Y,
+ pos.X >= 0f ? 0f : 1f,
+ pos.Y >= 0f ? 0f : 1f);
+ var size4 = new Vector4(size.X, size.Y, 0f, 0f);
+ Draw(pos4, size4, color, text);
+ }
+ }
+
+}
diff --git a/Demo1/Demo1/Game.ico b/Demo1/Demo1/Game.ico
new file mode 100644
index 0000000..8cff41e
Binary files /dev/null and b/Demo1/Demo1/Game.ico differ
diff --git a/Demo1/Demo1/Game1.cs b/Demo1/Demo1/Game1.cs
new file mode 100644
index 0000000..260bf0e
--- /dev/null
+++ b/Demo1/Demo1/Game1.cs
@@ -0,0 +1,248 @@
+
+using System;
+using System.IO;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Demo1
+{
+
+ public class Game1 : Microsoft.Xna.Framework.Game
+ {
+
+ private DrawableGameComponent pageComponent;
+ public Texture2D white;
+ private SpriteBatch spriteBatch;
+ private Font myFont;
+ private int pageNumber, pageNumberOld;
+ private bool paused;
+ private bool anyDrawText;
+
+ private float framesPerSecond = 60f;
+ private float countSeconds;
+ private int countFrames;
+
+
+ public Game1()
+ {
+ Content.RootDirectory = "Content";
+
+ // set up callbacks for window creation and resizing
+ Config.InitGraphics(this);
+ }
+
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+ Config.InitWindow(Window);
+ Storage.Init();
+ IsMouseVisible = true;
+ pageNumber = Storage.GetInt("Game_PageNumber", 1);
+ Components.Add(new Touch(this));
+ }
+
+ protected override void LoadContent()
+ {
+ spriteBatch = new SpriteBatch(GraphicsDevice);
+ myFont = new Font(this, spriteBatch, "MyFont");
+ white = Content.Load("white");
+
+ // Android does not have universal support for DXT compression,
+ // and DXT compression generally creates larger files than PNG.
+ // thus it may be preferrable to avoid DXT compression altogether
+ // and load the unprocessed PNG as below:
+ //
+ // var stream = TitleContainer.OpenStream(
+ // Content.RootDirectory + "/image.png");
+ // Texture2D.FromStream(GraphicsDevice, stream);
+ //
+ // to disable processing, open Properties on the image in the
+ // Content project, set "Build Action: None", and
+ // "Copy to Output Directory: Copy if newer".
+ }
+
+
+ protected override void UnloadContent()
+ {
+ }
+
+
+ protected override void Update(GameTime gameTime)
+ {
+ // when resuming the Android activity after it was paused, Update()
+ // may be invoked multiple times to "catch up" within a time span
+ // of up to half a second, and then Draw() will be called, and then
+ // normal processing is resumed.
+ // if this "catch up" is not desireable, a simple workaround is to
+ // set a "paused" flag in OnDeactivated(), and clear it in Draw().
+
+ if (paused)
+ return;
+
+ // basic scene management for the purpose of this demo:
+ // after either arrow at the top of the screen is clicked, and
+ // the current page number has changed, create the new 'page'.
+
+ if (pageNumber != pageNumberOld)
+ {
+ const int LAST_PAGE = 4;
+ if (pageNumber <= 0)
+ pageNumber = LAST_PAGE;
+ else if (pageNumber > LAST_PAGE)
+ pageNumber = 1;
+
+ pageNumberOld = pageNumber;
+ Storage.Set("Game_PageNumber", pageNumber);
+
+ if (pageComponent != null)
+ Components.Remove(pageComponent);
+
+ pageComponent = pageNumber switch
+ {
+ 1 => new SpriteDemo(this),
+ 2 => new CubeDemo(this),
+ 3 => new RenderDemo(this),
+ // the F# example is in project Demo1FSharp
+ 4 => new Demo1FSharp.StencilDemo(this),
+ _ => throw new InvalidOperationException(),
+ };
+ Components.Add(pageComponent);
+ }
+
+ base.Update(gameTime);
+ }
+
+
+ protected override void Draw(GameTime gameTime)
+ {
+ // BNA does not clear the screen at the start of the frame.
+ // since we are alpha blending, we need to make sure we clear.
+
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+ spriteBatch.Begin();
+
+ // display frames per second at the bottom of the screen.
+
+ countSeconds += (float) gameTime.ElapsedGameTime.TotalSeconds;
+ if (countSeconds > 1f)
+ {
+ framesPerSecond = countFrames / countSeconds;
+ countSeconds -= 1f;
+ countFrames = 0;
+ }
+ else
+ countFrames++;
+
+ var fps = framesPerSecond;
+ myFont.Draw(new Vector2(-0.7f, -0.2f), new Vector2(0.6f, 0.2f), Color.Yellow, $"FPS {fps:N2}");
+
+ // display the logo at the top of the screen
+
+ var ttl = $"BNA Demo (pg. {pageNumber})";
+ var rect = myFont.Measure(new Vector4(0f, 0f, 0.5f, 0f), new Vector4(1.25f, 0.3f, 0.5f, 0f), ttl);
+ spriteBatch.Draw(white, new Rectangle(0, 0, Window.ClientBounds.Width, (int) (rect.Height * 0.9f)), Color.Yellow);
+ myFont.Draw(rect, Color.Green, ttl);
+
+ // display the arrows at the top of the screen, and check for
+ // for taps. the Touch class uses the XNA Mouse class, which is
+ // simulated from the touch screen on Android. see Touch class.
+
+ var btn = "<<<";
+ rect = myFont.Measure(new Vector2(0f, 0.05f), new Vector2(0.5f, 0.2f), btn);
+ myFont.Draw(rect, Color.Black, btn);
+ if (Touch.Clicked(rect))
+ pageNumber--;
+
+ btn = ">>>";
+ rect = myFont.Measure(new Vector2(-0.5f, 0.05f), new Vector2(0.5f, 0.2f), btn);
+ myFont.Draw(rect, Color.Black, btn);
+ if (Touch.Clicked(rect))
+ pageNumber++;
+
+ spriteBatch.End();
+ base.Draw(gameTime);
+
+ // reset the "paused" flag. see comment at top of Update()
+ paused = false;
+ }
+
+
+ //
+ // utility methods for the 'page' components
+ //
+
+
+ public Rectangle DrawText(string txt, float x, float y, float w, float h)
+ {
+ if (! anyDrawText)
+ {
+ spriteBatch.Begin();
+ anyDrawText = true;
+ }
+
+ var rect = myFont.Measure(new Vector2(x, y), new Vector2(w, h), txt);
+ myFont.Draw(rect, Color.Black, txt);
+ rect.Offset(3, 3);
+ myFont.Draw(rect, Color.White, txt);
+ return rect;
+ }
+
+
+ public void DrawSprite(Texture2D sprite, Rectangle rect)
+ {
+ if (! anyDrawText)
+ {
+ spriteBatch.Begin();
+ anyDrawText = true;
+ }
+
+ spriteBatch.Draw(sprite, rect, Color.White);
+ }
+
+
+
+ public void DrawFlushBatch()
+ {
+ if (anyDrawText)
+ {
+ spriteBatch.End();
+ anyDrawText = false;
+ }
+ }
+
+
+
+ protected override void EndDraw()
+ {
+ DrawFlushBatch();
+ base.EndDraw();
+ }
+
+
+ protected override void OnActivated(object sender, EventArgs args)
+ {
+ }
+
+
+ protected override void OnDeactivated(object sender, EventArgs args)
+ {
+ // set the "paused" flag. see comment at top of Update()
+ paused = true;
+
+ // it is important to save the state when Android is pausing,
+ // and restore the state on start up, because we never know
+ // when we might get destroyed, but we know we will always
+ // get the OnDeactivated callback before destruction.
+ // see also: Storage::Init()
+ Storage.Sync();
+ }
+
+
+ protected override void OnExiting(object sender, EventArgs args)
+ {
+ // Storage.Clear();
+ }
+
+ }
+}
diff --git a/Demo1/Demo1/GameThumbnail.png b/Demo1/Demo1/GameThumbnail.png
new file mode 100644
index 0000000..462311a
Binary files /dev/null and b/Demo1/Demo1/GameThumbnail.png differ
diff --git a/Demo1/Demo1/Program.cs b/Demo1/Demo1/Program.cs
new file mode 100644
index 0000000..604a681
--- /dev/null
+++ b/Demo1/Demo1/Program.cs
@@ -0,0 +1,31 @@
+using Microsoft.Xna.Framework;
+using System;
+using System.Security.Principal;
+
+namespace com.spaceflint.bluebonnet.xnademo1
+{
+#if WINDOWS || XBOX
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ static void Main()
+ {
+ using (var game = new Demo1.Game1())
+ {
+ try
+ {
+ game.Run();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e.ToString());
+ System.Windows.Forms.MessageBox.Show(e.ToString());
+ }
+ }
+ }
+ }
+#endif
+}
+
diff --git a/Demo1/Demo1/Properties/AssemblyInfo.cs b/Demo1/Demo1/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..380d207
--- /dev/null
+++ b/Demo1/Demo1/Properties/AssemblyInfo.cs
@@ -0,0 +1,34 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Demo1")]
+[assembly: AssemblyProduct("Demo1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type. Only Windows
+// assemblies support COM.
+[assembly: ComVisible(false)]
+
+// On Windows, the following GUID is for the ID of the typelib if this
+// project is exposed to COM. On other platforms, it unique identifies the
+// title storage container when deploying this assembly to the device.
+[assembly: Guid("223c1770-ab2f-4278-b8e3-349580a51644")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+[assembly: AssemblyVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/Demo1/Demo1/RenderDemo.cs b/Demo1/Demo1/RenderDemo.cs
new file mode 100644
index 0000000..eac63e1
--- /dev/null
+++ b/Demo1/Demo1/RenderDemo.cs
@@ -0,0 +1,102 @@
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace Demo1
+{
+ public class RenderDemo : DrawableGameComponent
+ {
+
+ private bool renderToTexture;
+ private int renderTargetWidth, renderTargetHeight;
+ private RenderTarget2D renderTarget;
+ private Rectangle clickRectangle;
+
+ public RenderDemo(Game game) : base(game)
+ {
+ }
+
+
+ public override void Initialize()
+ {
+ renderToTexture = Storage.GetInt("RenderDemo_RenderToTexture", 0) != 0;
+ base.Initialize();
+ }
+
+
+ public override void Draw(GameTime gameTime)
+ {
+ // multiple draw calls, particular for drawing fonts, are slow.
+ // this example shows rendering a lot of text to one texture,
+ // and drawing just a single texture.
+
+ if (renderToTexture && renderTargetWidth == Config.ClientWidth
+ && renderTargetHeight == Config.ClientHeight)
+ {
+ ((Game1)Game).DrawSprite(renderTarget,
+ new Rectangle(0, 0, renderTargetWidth, renderTargetHeight));
+ }
+ else
+ {
+ if (renderToTexture)
+ {
+ ((Game1)Game).DrawFlushBatch();
+ renderTargetWidth = Config.ClientWidth;
+ renderTargetHeight = Config.ClientHeight;
+ if (renderTarget != null)
+ renderTarget.Dispose();
+ renderTarget = new RenderTarget2D(GraphicsDevice,
+ renderTargetWidth, renderTargetHeight,
+ true, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
+ GraphicsDevice.SetRenderTarget(renderTarget);
+ GraphicsDevice.Clear(Color.Transparent);
+ }
+
+ ReallyDraw();
+
+ if (renderToTexture)
+ {
+ ((Game1)Game).DrawFlushBatch();
+ GraphicsDevice.SetRenderTarget(null);
+ ((Game1)Game).DrawSprite(renderTarget,
+ new Rectangle(0, 0, renderTargetWidth, renderTargetHeight));
+ }
+ }
+
+ if (Touch.Clicked(clickRectangle))
+ {
+ renderToTexture = ! renderToTexture;
+ renderTargetWidth = -1;
+ renderTargetHeight = -1;
+ Storage.Set("RenderDemo_RenderToTexture", renderToTexture ? 1 : 0);
+ }
+ }
+
+
+ private void ReallyDraw()
+ {
+ float widthInInches = Config.ClientWidth / (float)Config.PixelsPerInch;
+ float heightInInches = Config.ClientHeight / (float)Config.PixelsPerInch - 1f;
+
+ for (int y = 0; y < 20; y++)
+ {
+ for (int x = 0; x < 20; x++)
+ {
+ string s = char.ConvertFromUtf32((int)'A' + (x + y) % 26);
+ float sx = 0.05f + widthInInches * 0.05f * x;
+ float sy = 0.7f + heightInInches * 0.05f * y;
+ ((Game1)Game).DrawText(s, sx, sy, 0.1f, 0.1f);
+ }
+ }
+
+ string what = renderToTexture ? "DISABLE" : "ENABLE";
+ clickRectangle = ((Game1)Game).DrawText(
+ $" TAP TO {what} RENDER TEXTURE ", 0f, 0.35f, widthInInches, 0.2f);
+
+ }
+
+ }
+
+}
diff --git a/Demo1/Demo1/SpriteDemo.cs b/Demo1/Demo1/SpriteDemo.cs
new file mode 100644
index 0000000..768d24f
--- /dev/null
+++ b/Demo1/Demo1/SpriteDemo.cs
@@ -0,0 +1,60 @@
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+
+namespace Demo1
+{
+ public class SpriteDemo : DrawableGameComponent
+ {
+
+ private Texture2D ball;
+ private SpriteBatch spriteBatch;
+ private int x, y;
+ private int dx, dy;
+
+
+ public SpriteDemo(Game game) : base(game)
+ {
+ spriteBatch = new SpriteBatch(game.GraphicsDevice);
+ }
+
+
+ public override void Initialize()
+ {
+ ball = Game.Content.Load("circle");
+
+ x = Storage.GetInt("SpriteDemo_X", Config.ClientWidth / 2);
+ y = Storage.GetInt("SpriteDemo_Y", Config.ClientHeight / 2);
+ dx = Storage.GetInt("SpriteDemo_DX", 1);
+ dy = Storage.GetInt("SpriteDemo_DY", 1);
+ }
+
+
+ public override void Update(GameTime gameTime)
+ {
+ if (x < 0 || x + Config.PixelsPerInch > Config.ClientWidth)
+ dx = -dx;
+ if (y < 0 || y + Config.PixelsPerInch > Config.ClientHeight)
+ dy = -dy;
+ x += dx * 2;
+ y += dy * 2;
+
+ Storage.Set("SpriteDemo_X", x);
+ Storage.Set("SpriteDemo_Y", y);
+ Storage.Set("SpriteDemo_DX", dx);
+ Storage.Set("SpriteDemo_DY", dy);
+ }
+
+
+ public override void Draw(GameTime gameTime)
+ {
+ spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
+ spriteBatch.Draw(ball, new Rectangle(x, y, Config.PixelsPerInch, Config.PixelsPerInch), Color.Red);
+ spriteBatch.End();
+ }
+
+ }
+
+}
diff --git a/Demo1/Demo1/Storage.cs b/Demo1/Demo1/Storage.cs
new file mode 100644
index 0000000..0f74a5b
--- /dev/null
+++ b/Demo1/Demo1/Storage.cs
@@ -0,0 +1,155 @@
+
+using System;
+using System.IO;
+using System.Text;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework.Storage;
+
+namespace Demo1
+{
+ public static class Storage
+ {
+
+ private const int VERSION_1 = 0x10101;
+ private static readonly int INTEGER = BitConverter.ToInt32(Encoding.ASCII.GetBytes("int "), 0);
+ private static readonly int FLOAT = BitConverter.ToInt32(Encoding.ASCII.GetBytes("flt "), 0);
+ private static readonly int STRING = BitConverter.ToInt32(Encoding.ASCII.GetBytes("str "), 0);
+
+ private static Stream file;
+ private static Dictionary dict;
+
+ public static void Init()
+ {
+ dict = new Dictionary();
+
+ // on Android, StorageDevice and StorageContainer are "thin"
+ // objects that do little more than translate the relative path
+ // specified in StorageContainer::OpenFile, to a full path in
+ // the app folder, which is then passed to System.IO.File.Open.
+
+ // the basic persistence model here is: game components update
+ // a dictionary with current values. the dictionary is written
+ // to a file on pause (Game::OnDeactivated calls Storage::Sync),
+ // and read from the file on startup (Game::Initialize calls
+ // Storage::Init).
+
+ try
+ {
+ var result = StorageDevice.BeginShowSelector(null, null);
+ result.AsyncWaitHandle.WaitOne();
+ var device = StorageDevice.EndShowSelector(result);
+ result.AsyncWaitHandle.Close();
+
+ result = device.BeginOpenContainer("Demo1", null, null);
+ result.AsyncWaitHandle.WaitOne();
+ var container = device.EndOpenContainer(result);
+ result.AsyncWaitHandle.Close();
+
+ file = container.OpenFile("state-v1.bin", FileMode.OpenOrCreate);
+ if (file.Length > 4)
+ Read(file);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Exception while reading state: " + e);
+ dict.Clear();
+ }
+ }
+
+ public static void Sync()
+ {
+ file.Position = 0;
+ try
+ {
+ Write(file);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Exception while writing state: " + e);
+ file.SetLength(0);
+ }
+ file.Flush();
+ }
+
+ public static void Clear()
+ {
+ dict.Clear();
+ Sync();
+ }
+
+ static void Read(Stream file)
+ {
+ using (var reader = new BinaryReader(file, Encoding.UTF8, true))
+ {
+ var version = reader.ReadInt32();
+ if (version == VERSION_1)
+ {
+ for (var count = reader.ReadInt32(); count > 0; count--)
+ {
+ var name = reader.ReadString();
+ var type = reader.ReadInt32();
+ var obj = (type == INTEGER) ? (object) reader.ReadInt32()
+ : (type == FLOAT) ? (object) reader.ReadSingle()
+ : (type == STRING) ? (object) reader.ReadString()
+ : null;
+ if (obj == null)
+ throw new NullReferenceException();
+ dict[name] = obj;
+ }
+ }
+ }
+ }
+
+ static void Write(Stream file)
+ {
+ using (var writer = new BinaryWriter(file, Encoding.UTF8, true))
+ {
+ writer.Write(VERSION_1);
+ writer.Write(dict.Count);
+ foreach (var kvp in dict)
+ {
+ writer.Write(kvp.Key);
+ if (kvp.Value is int intValue)
+ {
+ writer.Write(INTEGER);
+ writer.Write(intValue);
+ }
+ else if (kvp.Value is float floatValue)
+ {
+ writer.Write(FLOAT);
+ writer.Write(floatValue);
+ }
+ else if (kvp.Value is string stringValue)
+ {
+ writer.Write(STRING);
+ writer.Write(stringValue);
+ }
+ }
+ }
+ }
+
+ public static int GetInt(string name, int defValue = 0)
+ {
+ return dict.TryGetValue(name, out var v)
+ && (v is int intValue) ? intValue : defValue;
+ }
+
+ public static float GetFloat(string name, float defValue = 0f)
+ {
+ return dict.TryGetValue(name, out var v)
+ && (v is float floatValue) ? floatValue : defValue;
+ }
+
+ public static string GetString(string name, string defValue = "")
+ {
+ return dict.TryGetValue(name, out var v)
+ && (v is string stringValue) ? stringValue : defValue;
+ }
+
+ public static void Set(string name, object value)
+ {
+ dict[name] = value;
+ }
+
+ }
+}
diff --git a/Demo1/Demo1/Touch.cs b/Demo1/Demo1/Touch.cs
new file mode 100644
index 0000000..175f8fe
--- /dev/null
+++ b/Demo1/Demo1/Touch.cs
@@ -0,0 +1,80 @@
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Input.Touch;
+
+namespace Demo1
+{
+ public class Touch : DrawableGameComponent
+ {
+
+ private int pressX, pressY;
+ private int releaseX, releaseY;
+ private static Touch instance;
+ public static GestureSample LastGesture;
+
+
+ public Touch(Game game) : base(game)
+ {
+ pressX = int.MinValue;
+ releaseX = int.MinValue;
+ instance = this;
+
+ // TouchPanel is functional when running on Android
+ TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag;
+ }
+
+
+ protected override void Dispose(bool disposing)
+ {
+ instance = null;
+ base.Dispose(disposing);
+ }
+
+ public override void Draw(GameTime gameTime)
+ {
+ // this code is in Draw because Update may be invoked multiple times
+ // per frame, which might cause the loss of the occasional click
+
+ // on Android, the Mouse class tracks single-finger taps, so it can
+ // be used on both Windows and Android for simple input. for more
+ // advanced touch tracking, use TouchPanel and gestures.
+
+ var state = Mouse.GetState();
+ if (state.LeftButton == ButtonState.Pressed)
+ {
+ if (pressX == int.MinValue)
+ {
+ pressX = state.X;
+ pressY = state.Y;
+ }
+ }
+ else if (pressX != int.MinValue && releaseX == int.MinValue)
+ {
+ releaseX = state.X;
+ releaseY = state.Y;
+ }
+ else
+ {
+ pressX = int.MinValue;
+ releaseX = int.MinValue;
+ }
+
+ if (TouchPanel.IsGestureAvailable)
+ {
+ LastGesture = TouchPanel.ReadGesture();
+ Console.WriteLine($"Gesture {LastGesture.GestureType} at {LastGesture.Position}");
+ }
+ }
+
+
+ public bool _Clicked(Rectangle rect)
+ => rect.Contains(pressX, pressY) && rect.Contains(releaseX, releaseY);
+
+ public static bool Clicked(Rectangle rect) => instance._Clicked(rect);
+
+ }
+
+}
diff --git a/Demo1/Demo1/VertexPositionNormalTextureColor.cs b/Demo1/Demo1/VertexPositionNormalTextureColor.cs
new file mode 100644
index 0000000..9c8bb37
--- /dev/null
+++ b/Demo1/Demo1/VertexPositionNormalTextureColor.cs
@@ -0,0 +1,118 @@
+
+using System;
+using System.Runtime.InteropServices;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+
+namespace Demo1
+{
+ [Serializable]
+ [StructLayout(LayoutKind.Sequential, Pack = 1)]
+ public struct VertexPositionNormalTextureColor : IVertexType
+ {
+ VertexDeclaration IVertexType.VertexDeclaration
+ {
+ get
+ {
+ return VertexDeclaration;
+ }
+ }
+
+ public Vector3 Position;
+ public Vector3 Normal;
+ public Vector2 TextureCoordinate;
+ public Color Color;
+
+ public static readonly VertexDeclaration VertexDeclaration;
+
+ static VertexPositionNormalTextureColor()
+ {
+ VertexDeclaration = new VertexDeclaration(
+ new VertexElement[]
+ {
+ new VertexElement(
+ 0,
+ VertexElementFormat.Vector3,
+ VertexElementUsage.Position,
+ 0
+ ),
+ new VertexElement(
+ 12,
+ VertexElementFormat.Vector3,
+ VertexElementUsage.Normal,
+ 0
+ ),
+ new VertexElement(
+ 24,
+ VertexElementFormat.Vector2,
+ VertexElementUsage.TextureCoordinate,
+ 0
+ ),
+ new VertexElement(
+ 32,
+ VertexElementFormat.Color,
+ VertexElementUsage.Color,
+ 0
+ ),
+ }
+ );
+ }
+
+ public VertexPositionNormalTextureColor(
+ Vector3 position,
+ Vector3 normal,
+ Vector2 textureCoordinate,
+ Color color)
+ {
+ Position = position;
+ Normal = normal;
+ TextureCoordinate = textureCoordinate;
+ Color = color;
+ }
+
+ public override int GetHashCode()
+ {
+ // TODO: Fix GetHashCode
+ return 0;
+ }
+
+ public override string ToString()
+ {
+ return (
+ "{{Position:" + Position.ToString() +
+ " Normal:" + Normal.ToString() +
+ " TextureCoordinate:" + TextureCoordinate.ToString() +
+ " Color:" + Color.ToString() +
+ "}}"
+ );
+ }
+
+ public static bool operator ==(VertexPositionNormalTextureColor left, VertexPositionNormalTextureColor right)
+ {
+ return ( (left.Position == right.Position) &&
+ (left.Normal == right.Normal) &&
+ (left.TextureCoordinate == right.TextureCoordinate) &&
+ (left.Color == right.Color)
+ );
+ }
+
+ public static bool operator !=(VertexPositionNormalTextureColor left, VertexPositionNormalTextureColor right)
+ {
+ return !(left == right);
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ {
+ return false;
+ }
+ if (obj.GetType() != base.GetType())
+ {
+ return false;
+ }
+ return (this == ((VertexPositionNormalTextureColor) obj));
+ }
+
+ }
+}
diff --git a/Demo1/Demo1/app.config b/Demo1/Demo1/app.config
new file mode 100644
index 0000000..3dbff35
--- /dev/null
+++ b/Demo1/Demo1/app.config
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Demo1/Demo1Content/4x4.png b/Demo1/Demo1Content/4x4.png
new file mode 100644
index 0000000..324c31c
Binary files /dev/null and b/Demo1/Demo1Content/4x4.png differ
diff --git a/Demo1/Demo1Content/Demo1Content.contentproj b/Demo1/Demo1Content/Demo1Content.contentproj
new file mode 100644
index 0000000..cee89a8
--- /dev/null
+++ b/Demo1/Demo1Content/Demo1Content.contentproj
@@ -0,0 +1,75 @@
+
+
+ {E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}
+ {96E2B04D-8817-42c6-938A-82C39BA4D311};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Debug
+ x86
+ Library
+ Properties
+ v4.0
+ v4.0
+ ..\..\.obj\Demo1\Content
+ $(OutputPath)\Intermediate
+ Content
+ true
+
+
+ x86
+
+
+ x86
+
+
+ Demo1Content
+
+
+
+
+
+
+
+
+
+
+
+ circle
+ TextureImporter
+ TextureProcessor
+
+
+ 4x4
+ TextureImporter
+ TextureProcessor
+ False
+ False
+ NoChange
+
+
+ white
+ TextureImporter
+ TextureProcessor
+
+
+
+
+ MyFont
+ FontDescriptionImporter
+ FontDescriptionProcessor
+
+
+
+
+ fsharp256
+ TextureImporter
+ TextureProcessor
+
+
+
+
+
\ No newline at end of file
diff --git a/Demo1/Demo1Content/MyFont.spritefont b/Demo1/Demo1Content/MyFont.spritefont
new file mode 100644
index 0000000..4daa038
--- /dev/null
+++ b/Demo1/Demo1Content/MyFont.spritefont
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+ Consolas
+
+
+ 64
+
+
+ 0
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ ~
+
+
+
+
diff --git a/Demo1/Demo1Content/circle.png b/Demo1/Demo1Content/circle.png
new file mode 100644
index 0000000..c1d5ea2
Binary files /dev/null and b/Demo1/Demo1Content/circle.png differ
diff --git a/Demo1/Demo1Content/fsharp256.png b/Demo1/Demo1Content/fsharp256.png
new file mode 100644
index 0000000..f984490
Binary files /dev/null and b/Demo1/Demo1Content/fsharp256.png differ
diff --git a/Demo1/Demo1Content/white.png b/Demo1/Demo1Content/white.png
new file mode 100644
index 0000000..13c0d35
Binary files /dev/null and b/Demo1/Demo1Content/white.png differ
diff --git a/Demo1/Demo1FSharp/Demo1FSharp.fsproj b/Demo1/Demo1FSharp/Demo1FSharp.fsproj
new file mode 100644
index 0000000..a89d7e0
--- /dev/null
+++ b/Demo1/Demo1FSharp/Demo1FSharp.fsproj
@@ -0,0 +1,30 @@
+
+
+
+ Library
+ net461
+ latest
+ ..\..\.obj\Demo1\$(Configuration)\
+ $(OutputPath)\Intermediate
+ x86
+ AnyCPU;x86
+
+
+
+
+
+
+
+ C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.dll
+
+
+
+ C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.Game.dll
+
+
+
+ C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.Graphics.dll
+
+
+
+
diff --git a/Demo1/Demo1FSharp/Directory.Build.props b/Demo1/Demo1FSharp/Directory.Build.props
new file mode 100644
index 0000000..91a3da7
--- /dev/null
+++ b/Demo1/Demo1FSharp/Directory.Build.props
@@ -0,0 +1,5 @@
+
+
+ ..\..\.obj\Demo1\packages
+
+
diff --git a/Demo1/Demo1FSharp/Library1.fs b/Demo1/Demo1FSharp/Library1.fs
new file mode 100644
index 0000000..30f1236
--- /dev/null
+++ b/Demo1/Demo1FSharp/Library1.fs
@@ -0,0 +1,37 @@
+namespace Demo1FSharp
+
+open System
+open Microsoft.Xna.Framework
+open Microsoft.Xna.Framework.Graphics
+
+type StencilDemo (game : Game) =
+ inherit DrawableGameComponent(game)
+
+ let mutable spriteBatch = null
+ let mutable texture = null
+
+ // using option here solely to force a dependency on FSharp.Core.dll
+ let mutable rectFunc : (Game -> Rectangle) option = None
+ let mutable colorFunc : (GameTime -> Color) option = None
+
+ let logoRect (game : Game) =
+ let (sw, sh) = (game.Window.ClientBounds.Width, game.Window.ClientBounds.Height)
+ let (iw, ih) = ((int) ((single) sw * 0.75f), (int) ((single) sh * 0.75f))
+ let (ix, iy) = ((sw - iw) / 2, (sh - ih) / 2)
+ Rectangle(ix, iy, iw, ih)
+
+ let logoColor (gameTime : GameTime) =
+ Color(0.f,
+ (float32) (Math.Sin(gameTime.TotalGameTime.TotalMilliseconds * 0.001)),
+ (float32) (Math.Cos(gameTime.TotalGameTime.TotalMilliseconds * 0.001)))
+
+ override Game.Initialize() =
+ spriteBatch <- new SpriteBatch (game.GraphicsDevice)
+ texture <- game.Content.Load("fsharp256")
+ rectFunc <- Some logoRect
+ colorFunc <- Some logoColor
+
+ override Game.Draw gameTime =
+ spriteBatch.Begin ()
+ spriteBatch.Draw (texture, rectFunc.Value game, colorFunc.Value gameTime)
+ spriteBatch.End ()
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..67d27c6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 spaceflint7
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/MakeAPK.project b/MakeAPK.project
new file mode 100644
index 0000000..df42009
--- /dev/null
+++ b/MakeAPK.project
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+ OnOutputUpdated
+
+
+
+
+
+
+ %(ContentDir.Filename)%(ContentDir.Extension)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..00cf07f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+
+# Bluebonnet BNA
+
+This is a port of [FNA](https://fna-xna.github.io/) for use with [Bluebonnet](https://github.com/spaceflint7/bluebonnet) to build games for Android using the [XNA 4.0](https://en.wikipedia.org/wiki/Microsoft_XNA) libraries.
+
+**Bluebonnet** is an Android-compatible implementation of the .NET platform on top of the Java Virtual Machine. **Bluebonnet BNA** makes it possible to compile XNA games written in C# or F# to Android Java without any dependencies on native code libraries.
+
+## Building
+
+- Download and build the `Bluebonnet` compiler and its runtime library, `Baselib.jar`. For instructions, see [Bluebonnet README](https://github.com/spaceflint7/bluebonnet/blob/master/README.md).
+
+- Download the [FNA](https://github.com/FNA-XNA/FNA/archive/master.zip) source code. Build by typing the following command in the FNA root directory:
+
+ - `MSBuild FNA.csproj -p:Configuration=Release`
+
+ - If the build is successful, the file `FNA.DLL` will be generated in the `bin/Release` sub-directory of the FNA root directory.
+
+- Download this `BNA` project and build it by typing the following command in the BNA root directory:
+
+ - `MSBuild BNA -p:Configuration=Release -p:ANDROID_JAR=/path/to/Android.jar -p:BLUEBONNET_EXE=/path/to/Bluebonnet/executable -p:FNA_DLL=/path/to/FNA.DLL`
+
+ - The `ANDROID_JAR` property specifies the full path to an `Android.jar` file from the Android SDK distribution. `BNA` requires Android SDK version 18 or later.
+
+ - The `BLUEBONNET_EXE` property specifies the full path to the Bluebonnet compiler that you built in an earlier step.
+
+ - The `FNA_DLL` property specifies the full path to the FNA.DLL that you built in an earlier step. As noted earlier, this path should be `(FNA_DIR)/bin/Release/FNA.dll`.
+
+ - If the build is successful, the file `BNA.jar` will be generated in the `.obj` sub-directory of the repository root directory.
+
+## Building the Demo
+
+An example application `Demo1` is provided, which demonstrates some XNA functionality in C# and F#. It can be built using Visual Studio (solution file `Demo1.sln`), or from the command line:
+
+- Type `nuget restore Demo1` to restore packages using [nuget](https://www.nuget.org/downloads).
+
+- Type `msbuild Demo1 -p:Configuration=Release -p:Platform="x86"`
+
+- Test the program: `.obj\Demo1\Release\Demo1.exe`. (Note that this will create the directory `SavedGames\Demo1` in the `Documents` directory.)
+
+- To convert the built application to an Android APK, type the following command: (Note that this is a single-line command; line breaks were added for clarity.)
+
+ - MSBuild MakeAPK.project
+ -p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe
+ -p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll
+ -p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll
+ -p:CONTENT_DIR=.obj\Demo1\Release\Content
+ -p:ICON_PNG=Demo1\Demo1\GameThumbnail.png
+ -p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml
+ -p:KEYSTORE_FILE=.\my.keystore
+ -p:KEYSTORE_PWD=123456
+ -p:APK_OUTPUT=.obj\Demo1.apk
+ -p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk
+ -p:EXTRA_JAR_1=.obj\BNA.jar
+ -p:EXTRA_JAR_2=\path\to\Bluebonnet\Baselib.jar
+ -p:BLUEBONNET_EXE=\path\to\Bluebonnet.exe
+ -p:ANDROID_JAR=\path\to\Android\platforms\android-XX\android.jar
+ -p:ANDROID_BUILD=\path\to\Android\build-tools\30.0.2
+
+ - Make sure to specify the right paths for the Bluebonnet compiler (via the `BLUEBONNET_EXE` property), the Baselib support library (via the `EXTRA_JAR_2` property), the Android.jar file (via the `ANDROID_JAR` property) and the Android build-tools directory (via the `ANDROID_BUILD` property).
+
+ - The parameters are detailed at the top of the [MakeAPK.project](MakeAPK.project) file. See also the comments in the [AndroidManifest.xml](Demo1/AndroidManifest.xml) file, and comments throughout the `Demo1` source files.
+
+ - If the build is successful, the file `Demo1.apk` will be generated in the `.obj` sub-directory of the repository root directory.
+
+- The batch file `build_demo.bat` runs the steps discussed in this "Building the Demo" section.
+
+- Install the built APK to an Android device:
+
+ - `\path\to\Android\platform-tools\adb install -r .obj\Demo1.apk`
diff --git a/Solution.project b/Solution.project
new file mode 100644
index 0000000..50669fa
--- /dev/null
+++ b/Solution.project
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ $(MSBuildThisFileDirectory)
+ $(MSBuildThisFileDirectory)
+
+ Release
+ AnyCPU
+
+ Properties
+
+ 512
+ true
+ v4.7.2
+
+ 8.0
+ false
+ 4
+ false
+ prompt
+
+
+ true
+
+ $(MSBuildThisFileDirectory).obj\
+ $(ObjDir)$(AssemblyName)\$(Configuration)\
+ $(ObjDir)$(AssemblyName)\$(Configuration)\
+
+
+
+
+ pdbonly
+ true
+
+
+
+ true
+ full
+ false
+ DEBUG;TRACE
+
+
+
+ 4.7
+ portable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build_demo.bat b/build_demo.bat
new file mode 100644
index 0000000..cc555c4
--- /dev/null
+++ b/build_demo.bat
@@ -0,0 +1,54 @@
+@echo off
+if "%ANDROID_JAR%" == "" (
+ echo Missing environment variable ANDROID_JAR.
+ echo It should specify the full path to an Android.jar file in the platforms directory of the Android SDK.
+ goto :EOF
+)
+if "%ANDROID_BUILD%" == "" (
+ echo Missing environment variable ANDROID_BUILD.
+ echo It should specify the full path to a build-tools directory in the Android SDK.
+ goto :EOF
+)
+if "%FNA_DLL%" == "" (
+ echo Missing environment variable FNA_DLL.
+ echo It should specify the full path to the FNA.DLL file.
+ goto :EOF
+)
+if "%BLUEBONNET_EXE%" == "" (
+ echo Missing environment variable BLUEBONNET_EXE.
+ echo It should specify the full path to the Bluebonnet executable.
+ goto :EOF
+)
+if "%BLUEBONNET_LIB%" == "" (
+ echo Missing environment variable BLUEBONNET_LIB.
+ echo It should specify the full path to the Bluebonnet Baselib.jar file.
+ goto :EOF
+)
+
+echo ========================================
+echo Building BNA. Command:
+echo MSBuild BNA -p:Configuration=Release
+echo ========================================
+MSBuild BNA -p:Configuration=Release
+pause
+
+echo ========================================
+echo Building Demo1. Command:
+echo nuget restore Demo1
+echo msbuild Demo1 -p:Configuration=Release -p:Platform="x86"
+echo ========================================
+nuget restore Demo1
+msbuild Demo1 -p:Configuration=Release -p:Platform="x86"
+pause
+
+echo ========================================
+echo Converting Demo1 to APK. Command:
+echo MSBuild MakeAPK.project -p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe -p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll -p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll -p:CONTENT_DIR=.obj\Demo1\Release\Content -p:ICON_PNG=Demo1\Demo1\GameThumbnail.png -p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml -p:KEYSTORE_FILE=.\my.keystore -p:KEYSTORE_PWD=123456 -p:APK_OUTPUT=.obj\Demo1.apk -p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk -p:EXTRA_JAR_1=.obj\BNA.jar -p:EXTRA_JAR_2=%BLUEBONNET_LIB%
+echo ========================================
+MSBuild MakeAPK.project -p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe -p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll -p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll -p:CONTENT_DIR=.obj\Demo1\Release\Content -p:ICON_PNG=Demo1\Demo1\GameThumbnail.png -p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml -p:KEYSTORE_FILE=.\my.keystore -p:KEYSTORE_PWD=123456 -p:APK_OUTPUT=.obj\Demo1.apk -p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk -p:EXTRA_JAR_1=.obj\BNA.jar -p:EXTRA_JAR_2=%BLUEBONNET_LIB%
+
+echo ========================================
+echo All done
+echo ========================================
+
+:EOF
diff --git a/my.keystore b/my.keystore
new file mode 100644
index 0000000..6b0270d
Binary files /dev/null and b/my.keystore differ