Initial commit

This commit is contained in:
spaceflint 2020-11-15 11:14:42 +02:00
commit 0b2510826b
51 changed files with 6799 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.obj/
.vs/
packages/
/TODO

58
BNA/BNA.csproj Normal file
View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{53E4C9F9-CE12-4067-8336-663D3582DCBA}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>BNA</RootNamespace>
<AssemblyName>BNA</AssemblyName>
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- disable a warning about references having 'x86' architecture -->
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
</PropertyGroup>
<Import Project="..\Solution.project" />
<ItemGroup>
<Reference Include="$(ObjDir)Android.dll" />
<Reference Include="$(FNA_DLL)" />
<Compile Include="src\**\*.cs" />
<CustomAdditionalCompileInputs Include="FNA.filter" />
<Filter Include="FNA.filter" />
</ItemGroup>
<!-- check prerequisites -->
<Target Name="CheckPreReqs" BeforeTargets="ImportAndroid">
<Error Condition="(! Exists($(ANDROID_JAR)))"
Text="Cannot find Android JAR at '$(ANDROID_JAR)' using property ANDROID_JAR."/>
<Error Condition="(! Exists($(BLUEBONNET_EXE)))"
Text="Cannot find Bluebonnet EXE at '$(BLUEBONNET_EXE)' using property BLUEBONNET_EXE."/>
<Error Condition="(! Exists($(FNA_DLL)))"
Text="Cannot find FNA DLL at '$(FNA_DLL)' using environment variable FNA_DLL."/>
</Target>
<!-- import android.jar into android.dll -->
<Target Name="ImportAndroid" BeforeTargets="ResolveAssemblyReferences"
Inputs="$(ANDROID_JAR)" Outputs="$(ObjDir)android.dll">
<Delete Files="$(ObjDir)android.dll" />
<Exec Command="&quot;$(BLUEBONNET_EXE)&quot; &quot;$(ANDROID_JAR)&quot; &quot;$(ObjDir)android.dll&quot;" />
</Target>
<!-- middle of the build; compile BNA.dll here -->
<!-- run our converter on BNA.dll to produce BNA.jar -->
<Target Name="ExportToJar" AfterTargets="AfterBuild"
Condition=" '$(_AssemblyTimestampBeforeCompile)' != '$(_AssemblyTimestampAfterCompile)'"
Inputs="$(OutputPath)$(AssemblyName).dll" Outputs="$(ObjDir)$(AssemblyName).jar">
<Delete Files="$(ObjDir)$(AssemblyName).jar" />
<Exec Command="&quot;$(BLUEBONNET_EXE)&quot; &quot;$(OutputPath)$(AssemblyName).dll&quot; &quot;$(ObjDir)$(AssemblyName).jar&quot;" />
<!-- run our converter on types from the FNA DLL and insert into BA.jar -->
<ReadLinesFromFile File="@(Filter)">
<Output TaskParameter="Lines" ItemName="FilterItem" />
</ReadLinesFromFile>
<PropertyGroup>
<FilterProp>%22:@(FilterItem, '%22 %22:')%22</FilterProp>
</PropertyGroup>
<Exec Command="&quot;$(BLUEBONNET_EXE)&quot; &quot;$(FNA_DLL)&quot; &quot;$(ObjDir)$(AssemblyName).jar&quot; $(FilterProp)" />
</Target>
<Target Name="CleanGamelibInSolutionOutputDirectory" AfterTargets="Clean">
<Delete Files="$(ObjDir)android.dll" />
<Delete Files="$(ObjDir)$(AssemblyName).dll" />
<Delete Files="$(ObjDir)$(AssemblyName).pdb" />
<Delete Files="$(ObjDir)$(AssemblyName).jar" />
</Target>
</Project>

104
BNA/FNA.filter Normal file
View File

@ -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.*

119
BNA/src/Activity.cs Normal file
View File

@ -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;
}
}

881
BNA/src/Effect.cs Normal file
View File

@ -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<EffectPass>();
passes.Add(new EffectPass(name, null, this, IntPtr.Zero, 0));
var list = new List<EffectTechnique>();
technique = new EffectTechnique(name, IntPtr.Zero,
new EffectPassCollection(passes), null);
list.Add(technique);
Techniques = new EffectTechniqueCollection(list);
}
//
// CollectUniforms
//
void CollectUniforms()
{
var list = new List<EffectParameter>();
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; }
}
}

27
BNA/src/Empty.cs Normal file
View File

@ -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;
}
}
}

577
BNA/src/FNA3D.cs Normal file
View File

@ -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;
}
}

498
BNA/src/FNA3D_Buf.cs Normal file
View File

@ -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<int, int[]> BufferSizeUsage = new Dictionary<int, int[]>();
public Dictionary<int, java.nio.Buffer> BufferCache = new Dictionary<int, java.nio.Buffer>();
}
}
}

140
BNA/src/FNA3D_Dev.cs Normal file
View File

@ -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;
}
}
}

247
BNA/src/FNA3D_Rt.cs Normal file
View File

@ -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;
}
}
}

577
BNA/src/FNA3D_Tex.cs Normal file
View File

@ -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<int, int[]> TextureConfigs = new Dictionary<int, int[]>();
}
}
}

172
BNA/src/FNAPlatform.cs Normal file
View File

@ -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<DisplayMode>();
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];
}
}

413
BNA/src/GameRunner.cs Normal file
View File

@ -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) { }
}
}

228
BNA/src/Import.cs Normal file
View File

@ -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<DisplayMode> setmodes) { }
}
//
// GraphicsAdapter
//
[java.attr.Discard] // discard in output
public sealed class GraphicsAdapter
{
public GraphicsAdapter(DisplayModeCollection modes, string name, string description) { }
public static ReadOnlyCollection<GraphicsAdapter> 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<EffectParameter> 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<EffectPass> 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<EffectTechnique> 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) { }
}
}

73
BNA/src/MessageBox.cs Normal file
View File

@ -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<DialogResult> 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());
}
}
}

188
BNA/src/Mouse.cs Normal file
View File

@ -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) { }
}
}

337
BNA/src/Renderer.cs Normal file
View File

@ -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<Renderer> GetRenderersForActivity(android.app.Activity activity)
{
var list = new List<Renderer>();
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<RendererObject> RendererObjects = new List<RendererObject>();
private class RendererObject
{
public long deviceId;
public Renderer renderer;
public java.lang.@ref.WeakReference activity;
}
}
}

188
BNA/src/Resources.cs Normal file
View File

@ -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 ---
");
}
}

62
BNA/src/TitleContainer.cs Normal file
View File

@ -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();
}
}
}

56
Demo1/AndroidManifest.xml Normal file
View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- specify the lowercase namespace of the project
as the package name in the manifest element -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.spaceflint.bluebonnet.xnademo1">
<!-- API 18, GLES 3.0 -->
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="29" />
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
<!-- modify android:label to set the application name.
this name appears in the Settings / Apps listing -->
<application android:label="BNA_Demo1"
android:icon="@drawable/icon"
android:isGame="true" >
<!-- set android:screenOrientation if you need to lock orientation:
https://developer.android.com/guide/topics/manifest/activity-element#screen -->
<!-- modify android:label to set the activity name.
this is the name that appears below the app icon. -->
<activity android:name="microsoft.xna.framework.Activity"
android:label="BNA Demo1"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:immersive="true"
android:launchMode="singleTask"
>
<!-- microsoft.xna.framework.log.tag sets the log identifier
for log messages printed via android.util.Log by the app.
if omitted, the default is 'BNA_Game' -->
<meta-data android:name="microsoft.xna.framework.log.tag"
android:value="BNA_Demo1"/>
<!-- microsoft.xna.framework.main.class sets the name of the main
or entrypoint class. if starts with a dot, it is appended
to the namespace specifies in the package attribute. -->
<meta-data android:name="microsoft.xna.framework.main.class"
android:value=".Program"/>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

47
Demo1/Demo1.sln Normal file
View File

@ -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

74
Demo1/Demo1/Config.cs Normal file
View File

@ -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");
}
}
}

184
Demo1/Demo1/CubeDemo.cs Normal file
View File

@ -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<Texture2D>("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;
}
}
}

110
Demo1/Demo1/Demo1.csproj Normal file
View File

@ -0,0 +1,110 @@
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{223C1770-AB2F-4278-B8E3-349580A51644}</ProjectGuid>
<ProjectTypeGuids>{6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Demo1</RootNamespace>
<AssemblyName>Demo1</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
<XnaFrameworkVersion>v4.0</XnaFrameworkVersion>
<XnaPlatform>Windows</XnaPlatform>
<XnaProfile>HiDef</XnaProfile>
<XnaCrossPlatformGroupID>ed304386-2da8-41d9-bfb0-3d6469f2ace6</XnaCrossPlatformGroupID>
<XnaOutputType>Game</XnaOutputType>
<ApplicationIcon>Game.ico</ApplicationIcon>
<Thumbnail>GameThumbnail.png</Thumbnail>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\.obj\Demo1\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE;WINDOWS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>x86</PlatformTarget>
<XnaCompressContent>false</XnaCompressContent>
<LangVersion>8.0</LangVersion>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\.obj\Demo1\Release\</OutputPath>
<DefineConstants>TRACE;WINDOWS</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<NoStdLib>true</NoStdLib>
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>x86</PlatformTarget>
<XnaCompressContent>true</XnaCompressContent>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup>
<IntermediateOutputPath>$(OutputPath)\Intermediate</IntermediateOutputPath>
<StartupObject />
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Xna.Framework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
<Reference Include="Microsoft.Xna.Framework.Game, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
<Reference Include="Microsoft.Xna.Framework.Graphics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=x86" />
<Reference Include="Microsoft.Xna.Framework.Input.Touch, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Xna.Framework.Storage, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="Config.cs" />
<Compile Include="CubeDemo.cs" />
<Compile Include="Font.cs" />
<Compile Include="RenderDemo.cs" />
<Compile Include="SpriteDemo.cs" />
<Compile Include="Storage.cs" />
<Compile Include="Touch.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Program.cs" />
<Compile Include="Game1.cs" />
<Compile Include="VertexPositionNormalTextureColor.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Game.ico" />
<Content Include="GameThumbnail.png">
<XnaPlatformSpecific>true</XnaPlatformSpecific>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Demo1Content\Demo1Content.contentproj">
<Name>Demo1Content</Name>
<XnaReferenceType>Content</XnaReferenceType>
</ProjectReference>
<ProjectReference Include="..\Demo1FSharp\Demo1FSharp.fsproj">
<Project>{e3649229-b2dd-4ce8-af10-7814cd306ea5}</Project>
<Name>Demo1FSharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\Microsoft.Xna.GameStudio.targets" />
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
<Target Name="Install" DependsOnTargets="AfterBuild">
<MSBuild Projects="../../Android.project" Targets="Install" Properties="Configuration=Release;&#xD;&#xA; GameAssembly=.obj\Demo1\Debug\Demo1.exe;&#xD;&#xA; AndroidManifest=Demo1\AndroidManifest.xml;&#xD;&#xA; KeystoreFile=my.keystore;KeystorePassword=123456;&#xD;&#xA; OutputApk=.obj\Demo1.apk" />
</Target>
</Project>

85
Demo1/Demo1/Font.cs Normal file
View File

@ -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<SpriteFont>(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);
}
}
}

BIN
Demo1/Demo1/Game.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

248
Demo1/Demo1/Game1.cs Normal file
View File

@ -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<Texture2D>("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();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

31
Demo1/Demo1/Program.cs Normal file
View File

@ -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
{
/// <summary>
/// The main entry point for the application.
/// </summary>
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
}

View File

@ -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")]

102
Demo1/Demo1/RenderDemo.cs Normal file
View File

@ -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);
}
}
}

60
Demo1/Demo1/SpriteDemo.cs Normal file
View File

@ -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<Texture2D>("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();
}
}
}

155
Demo1/Demo1/Storage.cs Normal file
View File

@ -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<string, object> dict;
public static void Init()
{
dict = new Dictionary<string, object>();
// 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;
}
}
}

80
Demo1/Demo1/Touch.cs Normal file
View File

@ -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);
}
}

View File

@ -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));
}
}
}

3
Demo1/Demo1/app.config Normal file
View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/></startup></configuration>

BIN
Demo1/Demo1Content/4x4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 B

View File

@ -0,0 +1,75 @@
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{E50B6FE1-C91C-4226-BA4B-75DEFDEF4CA3}</ProjectGuid>
<ProjectTypeGuids>{96E2B04D-8817-42c6-938A-82C39BA4D311};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">x86</Platform>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<XnaFrameworkVersion>v4.0</XnaFrameworkVersion>
<OutputPath>..\..\.obj\Demo1\Content</OutputPath>
<IntermediateOutputPath>$(OutputPath)\Intermediate</IntermediateOutputPath>
<ContentRootDirectory>Content</ContentRootDirectory>
<DisableContentItemWarning>true</DisableContentItemWarning>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<PlatformTarget>x86</PlatformTarget>
</PropertyGroup>
<PropertyGroup>
<RootNamespace>Demo1Content</RootNamespace>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.Xna.Framework.Content.Pipeline.EffectImporter, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Xna.Framework.Content.Pipeline.FBXImporter, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Xna.Framework.Content.Pipeline.TextureImporter, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Xna.Framework.Content.Pipeline.XImporter, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Xna.Framework.Content.Pipeline.AudioImporters, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
<Reference Include="Microsoft.Xna.Framework.Content.Pipeline.VideoImporters, Version=4.0.0.0, Culture=neutral, PublicKeyToken=842cf8be1de50553, processorArchitecture=MSIL" />
</ItemGroup>
<ItemGroup>
<Compile Include="circle.png">
<Name>circle</Name>
<Importer>TextureImporter</Importer>
<Processor>TextureProcessor</Processor>
</Compile>
<Compile Include="4x4.png">
<Name>4x4</Name>
<Importer>TextureImporter</Importer>
<Processor>TextureProcessor</Processor>
<ProcessorParameters_ColorKeyEnabled>False</ProcessorParameters_ColorKeyEnabled>
<ProcessorParameters_PremultiplyAlpha>False</ProcessorParameters_PremultiplyAlpha>
<ProcessorParameters_TextureFormat>NoChange</ProcessorParameters_TextureFormat>
</Compile>
<Compile Include="white.png">
<Name>white</Name>
<Importer>TextureImporter</Importer>
<Processor>TextureProcessor</Processor>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="MyFont.spritefont">
<Name>MyFont</Name>
<Importer>FontDescriptionImporter</Importer>
<Processor>FontDescriptionProcessor</Processor>
</Compile>
</ItemGroup>
<ItemGroup>
<Compile Include="fsharp256.png">
<Name>fsharp256</Name>
<Importer>TextureImporter</Importer>
<Processor>TextureProcessor</Processor>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\XNA Game Studio\$(XnaFrameworkVersion)\Microsoft.Xna.GameStudio.ContentPipeline.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file contains an xml description of a font, and will be read by the XNA
Framework Content Pipeline. Follow the comments to customize the appearance
of the font in your game, and to change the characters which are available to draw
with.
-->
<XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics">
<Asset Type="Graphics:FontDescription">
<!--
Modify this string to change the font that will be imported.
-->
<FontName>Consolas</FontName>
<!--
Size is a float value, measured in points. Modify this value to change
the size of the font.
-->
<Size>64</Size>
<!--
Spacing is a float value, measured in pixels. Modify this value to change
the amount of spacing in between characters.
-->
<Spacing>0</Spacing>
<!--
UseKerning controls the layout of the font. If this value is true, kerning information
will be used when placing characters.
-->
<UseKerning>true</UseKerning>
<!--
Style controls the style of the font. Valid entries are "Regular", "Bold", "Italic",
and "Bold, Italic", and are case sensitive.
-->
<Style>Regular</Style>
<!--
If you uncomment this line, the default character will be substituted if you draw
or measure text that contains characters which were not included in the font.
-->
<!-- <DefaultCharacter>*</DefaultCharacter> -->
<!--
CharacterRegions control what letters are available in the font. Every
character from Start to End will be built and made available for drawing. The
default range is from 32, (ASCII space), to 126, ('~'), covering the basic Latin
character set. The characters are ordered according to the Unicode standard.
See the documentation for more information.
-->
<CharacterRegions>
<CharacterRegion>
<Start>&#32;</Start>
<End>&#126;</End>
</CharacterRegion>
</CharacterRegions>
</Asset>
</XnaContent>

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 B

View File

@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net461</TargetFramework>
<LangVersion>latest</LangVersion>
<OutputPath>..\..\.obj\Demo1\$(Configuration)\</OutputPath>
<IntermediateOutputPath>$(OutputPath)\Intermediate</IntermediateOutputPath>
<PlatformTarget>x86</PlatformTarget>
<Platforms>AnyCPU;x86</Platforms>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library1.fs" />
<Reference Include="Microsoft.Xna.Framework">
<HintPath>C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Xna.Framework.Game">
<HintPath>C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.Game.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Xna.Framework.Graphics">
<HintPath>C:\Program Files (x86)\Microsoft XNA\XNA Game Studio\v4.0\References\Windows\x86\Microsoft.Xna.Framework.Graphics.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -0,0 +1,5 @@
<Project>
<PropertyGroup>
<MSBuildProjectExtensionsPath>..\..\.obj\Demo1\packages</MSBuildProjectExtensionsPath>
</PropertyGroup>
</Project>

View File

@ -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 ()

21
LICENSE Normal file
View File

@ -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.

104
MakeAPK.project Normal file
View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- REQUIRED PROPERTIES / ENVIRONMENT VARIABLES
INPUT_DLL = semi-colon separated list of DLLs to convert using Bluebonnet
e.g. /path/to/MyGame.exe;/path/to/MySupportLib.dll
INPUT_DLL_1 .. INPUT_DLL_9 = same as INPUT_DLL
EXTRA_JAR = semi-colon separated list of additional JARs to bundle in the APK
e.g. /path/to/Baselib.jar;/path/to/BNA.jar
EXTRA_JAR_1 .. EXTRA_JAR_9 = same as EXTRA_JAR
CONTENT_DIR = path to Content directory
e.g. $(OutputDir)/MyGame/Debug/Content
APK_OUTPUT = path to copy the final APK
e.g. $(OutputDir)/MyGame.apk
APK_TEMP_DIR = directory where APK processing occurs
e.g. $(OutputDir)/MyGame/Debug/Content
KEYSTORE_FILE = path to a keystore file used in APK signing
KEYSTORE_PWD = password for keystore file
ANDROID_MANIFEST = path to AndroidManifest.xml file
BLUEBONNET_EXE = path to Bluebonnet.exe program file
BLUEBONNET_JAR = path to Baselib.jar file from Bluebonnet
ANDROID_JAR = path to Android.jar file, for desired API level
e.g. $(ANDROID_HOME)/android-28/android.jar
ANDROID_BUILD = path to a build tools directory, for desired tools version
e.g. $(ANDROID_HOME)/build-tools/30.0.2
-->
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
</PropertyGroup>
<ItemGroup>
<InputDll Include="$(INPUT_DLL);$(INPUT_DLL_1);$(INPUT_DLL_2);$(INPUT_DLL_3);$(INPUT_DLL_4);$(INPUT_DLL_5);$(INPUT_DLL_6);$(INPUT_DLL_7);$(INPUT_DLL_8);$(INPUT_DLL_9)" />
<ExtraJar Include="$(EXTRA_JAR);$(EXTRA_JAR_1);$(EXTRA_JAR_2);$(EXTRA_JAR_3);$(EXTRA_JAR_4);$(EXTRA_JAR_5);$(EXTRA_JAR_6);$(EXTRA_JAR_7);$(EXTRA_JAR_8);$(EXTRA_JAR_9)" />
<ContentDir Include="$(CONTENT_DIR)">
<DirName>%(ContentDir.Filename)%(ContentDir.Extension)</DirName>
</ContentDir>
<ContentFiles Include="$(CONTENT_DIR)/**/*" />
</ItemGroup>
<Target Name="CoreCompile">
<Error Condition="(! Exists($(ICON_PNG)))"
Text="Cannot find icon PNG file at '$(ICON_PNG)' using property ICON_PNG."/>
<Error Condition="(! Exists($(CONTENT_DIR)))"
Text="Cannot find game content folder at '$(CONTENT_DIR)' using property CONTENT_DIR."/>
<Error Condition="(! Exists($(ANDROID_MANIFEST)))"
Text="Cannot find Android manifest file at '$(ANDROID_MANIFEST)' using property ANDROID_MANIFEST."/>
<Error Condition="(! Exists($(KEYSTORE_FILE)))"
Text="Cannot find keystroke file at '$(KEYSTORE_FILE)' using property KEYSTORE_FILE."/>
<Error Condition="(! Exists($(BLUEBONNET_EXE)))"
Text="Cannot find Bluebonnet program file at '$(BLUEBONNET_EXE)' using property BLUEBONNET_EXE."/>
<Error Condition="(! Exists($(ANDROID_JAR)))"
Text="Cannot find Android platform JAR file at '$(ANDROID_JAR)' using property ANDROID_JAR."/>
<Error Condition="(! Exists('$(ANDROID_BUILD)/aapt.exe')) and (! Exists('$(ANDROID_BUILD)/aapt'))"
Text="Cannot find Android build tools at '$(ANDROID_BUILD)' using property ANDROID_BUILD."/>
<Error Condition="'$(APK_OUTPUT)' == ''"
Text="The APK_OUTPUT property should specify the output location."/>
<Error Condition="'$(APK_TEMP_DIR)' == ''"
Text="The APK_TEMP_DIR property should specify a temporary directory."/>
<Error Condition="'$(KEYSTORE_PWD)' == ''"
Text="The KEYSTORE_PWD property should specify a the keystore password."/>
<CreateProperty Value="%(ContentDir.DirName)">
<Output TaskParameter="Value" PropertyName="CONTENT_SUBDIR" />
</CreateProperty>
<RemoveDir Directories="$(APK_TEMP_DIR)" />
<MakeDir Directories="$(APK_TEMP_DIR)" />
<Exec Command="&quot;$(BLUEBONNET_EXE)&quot; &quot;%(InputDll.FullPath)&quot; &quot;$(APK_TEMP_DIR)/classes.jar&quot;" />
<Exec Command="&quot;$(ANDROID_BUILD)/d8&quot; --release --lib &quot;$(ANDROID_JAR)&quot; &quot;$(APK_TEMP_DIR)/classes.jar&quot; &quot;@(ExtraJar,'&quot; &quot;')&quot; --output &quot;$(APK_TEMP_DIR)&quot;"
Condition="'@(ExtraJar)' != ''" />
<Exec Command="&quot;$(ANDROID_BUILD)/d8&quot; --release --lib &quot;$(ANDROID_JAR)&quot; &quot;$(APK_TEMP_DIR)/classes.jar&quot; --output &quot;$(APK_TEMP_DIR)&quot;"
Condition="'@(ExtraJar)' == ''" />
<Copy SourceFiles="@(ContentFiles)" DestinationFolder="$(APK_TEMP_DIR)/assets/$(CONTENT_SUBDIR)" SkipUnchangedFiles="true" />
<Copy SourceFiles="$(ICON_PNG)" DestinationFiles="$(APK_TEMP_DIR)/res/drawable/icon.png" SkipUnchangedFiles="true" />
<Exec Command="&quot;$(ANDROID_BUILD)/aapt&quot; package -f -F &quot;$(APK_TEMP_DIR)/unaligned.apk&quot; -M &quot;$(ANDROID_MANIFEST)&quot; -S &quot;$(APK_TEMP_DIR)/res&quot; -I &quot;$(ANDROID_JAR)&quot;" />
<Exec Command="&quot;$(ANDROID_BUILD)/aapt&quot; add &quot;unaligned.apk&quot; classes.dex" WorkingDirectory="$(APK_TEMP_DIR)" />
<Exec Command="&quot;$(ANDROID_BUILD)/aapt&quot; add &quot;unaligned.apk&quot; assets/$(CONTENT_SUBDIR)/%(ContentFiles.RecursiveDir)%(ContentFiles.Filename)%(ContentFiles.Extension)" WorkingDirectory="$(APK_TEMP_DIR)" />
<Exec Command="&quot;$(ANDROID_BUILD)/zipalign&quot; -f 4 &quot;$(APK_TEMP_DIR)/unaligned.apk&quot; &quot;$(APK_TEMP_DIR)/aligned.apk&quot;" />
<Exec Command="&quot;$(ANDROID_BUILD)/apksigner&quot; sign --ks &quot;$(KEYSTORE_FILE)&quot; --ks-pass &quot;pass:$(KEYSTORE_PWD)&quot; &quot;$(APK_TEMP_DIR)/aligned.apk&quot;" />
<!-- copy the final APK -->
<Copy SourceFiles="$(APK_TEMP_DIR)/aligned.apk" DestinationFiles="$(APK_OUTPUT)" />
</Target>
</Project>

69
README.md Normal file
View File

@ -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.)
- <code>MSBuild MakeAPK.project<br>
-p:INPUT_DLL=.obj\Demo1\Release\Demo1.exe<br>
-p:INPUT_DLL_2=.obj\Demo1\Release\Demo1FSharp.dll<br>
-p:INPUT_DLL_3=.obj\Demo1\Release\FSharp.Core.dll<br>
-p:CONTENT_DIR=.obj\Demo1\Release\Content<br>
-p:ICON_PNG=Demo1\Demo1\GameThumbnail.png<br>
-p:ANDROID_MANIFEST=Demo1\AndroidManifest.xml<br>
-p:KEYSTORE_FILE=.\my.keystore<br>
-p:KEYSTORE_PWD=123456<br>
-p:APK_OUTPUT=.obj\Demo1.apk<br>
-p:APK_TEMP_DIR=.obj\Demo1\Release\TempApk<br>
-p:EXTRA_JAR_1=.obj\BNA.jar<br>
-p:EXTRA_JAR_2=\path\to\Bluebonnet\Baselib.jar<br>
-p:BLUEBONNET_EXE=\path\to\Bluebonnet.exe<br>
-p:ANDROID_JAR=\path\to\Android\platforms\android-XX\android.jar<br>
-p:ANDROID_BUILD=\path\to\Android\build-tools\30.0.2</code>
- 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`

64
Solution.project Normal file
View File

@ -0,0 +1,64 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props"
Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<SolutionDir Condition="'$(SolutionDir)' == ''">$(MSBuildThisFileDirectory)</SolutionDir>
<SolutionDir Condition="'$(SolutionDir)' == '*Undefined*'">$(MSBuildThisFileDirectory)</SolutionDir>
<Configuration Condition=" '$(Configuration)' == '' ">Release</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<AppDesignerFolder>Properties</AppDesignerFolder>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<TargetFrameworkProfile></TargetFrameworkProfile>
<LangVersion>8.0</LangVersion>
<Prefer32Bit>false</Prefer32Bit>
<WarningLevel>4</WarningLevel>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<ErrorReport>prompt</ErrorReport>
<!-- skip temporary directory when building -->
<SkipCopyBuildProduct>true</SkipCopyBuildProduct>
<ObjDir>$(MSBuildThisFileDirectory).obj\</ObjDir>
<OutputPath>$(ObjDir)$(AssemblyName)\$(Configuration)\</OutputPath>
<IntermediateOutputPath>$(ObjDir)$(AssemblyName)\$(Configuration)\</IntermediateOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(ProjectLanguage)' == 'FSharp' ">
<LangVersion>4.7</LangVersion>
<DebugType>portable</DebugType>
</PropertyGroup>
<Target Name="CleanProjectInSolutionOutputDirectory" AfterTargets="Clean">
<RemoveDir Directories="$(ObjDir)$(AssemblyName)\$(Configuration)" />
<RemoveDir Directories="$(ObjDir)$(AssemblyName)" />
</Target>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets"
Condition=" '$(ProjectLanguage)' == 'CSharp' or '$(ProjectLanguage)' == '' " />
<Import Project="$(FSHARPINSTALLDIR)\Microsoft.FSharp.targets"
Condition=" '$(ProjectLanguage)' == 'FSharp' and '$(FSHARPINSTALLDIR)' != '' " />
<Import Project="$(FSharpCompilerPath)\Microsoft.FSharp.targets"
Condition=" '$(ProjectLanguage)' == 'FSharp' and '$(FSHARPINSTALLDIR)' == '' and $(FSharpCompilerPath) != '' " />
</Project>

54
build_demo.bat Normal file
View File

@ -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

BIN
my.keystore Normal file

Binary file not shown.