Various fixes and improvements

This commit is contained in:
spaceflint 2021-02-13 23:22:56 +02:00
parent 803415c988
commit f9a8376b93
17 changed files with 608 additions and 134 deletions

View File

@ -45,6 +45,10 @@
*.Input.Touch.TouchLocationState *.Input.Touch.TouchLocationState
*.Input.Touch.TouchPanel *.Input.Touch.TouchPanel
*.Input.Touch.TouchPanelCapabilities *.Input.Touch.TouchPanelCapabilities
*.Input.Keyboard
*.Input.KeyboardState
*.Input.Keys
*.Input.KeyState
*.Graphics.BasicEffect *.Graphics.BasicEffect
*.Graphics.Blend *.Graphics.Blend

View File

@ -11,11 +11,45 @@ namespace Microsoft.Xna.Framework
protected override void onCreate(android.os.Bundle savedInstanceState) protected override void onCreate(android.os.Bundle savedInstanceState)
{ {
// on some devices, this should be before call to base.onCreate
// requestWindowFeature(android.view.Window.FEATURE_NO_TITLE);
logTag = GetMetaAttr_Str("log.tag", "BNA_Game");
backKeyCode = GetMetaAttr_Int("back.key");
if (android.os.Build.VERSION.SDK_INT >= 19)
{
immersiveMode = GetMetaAttr_Int("immersive.mode") != 0;
if (immersiveMode && android.os.Build.VERSION.SDK_INT >= 28)
{
var layoutParams = getWindow().getAttributes();
layoutParams.layoutInDisplayCutoutMode =
android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
}
if (GetMetaAttr_Int("keep.screen.on") != 0)
{
getWindow().addFlags(
android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
/*
int flags = android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN
| android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
| android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
if (GetMetaAttr_Int("keep.screen.on") != 0)
flags |= android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
getWindow().setFlags(flags
| android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
flags);
*/
base.onCreate(savedInstanceState); base.onCreate(savedInstanceState);
_LogTag = GetMetaAttr("log.tag") ?? _LogTag;
new java.lang.Thread(gameRunner = new GameRunner(this)).start();
} }
// //
@ -36,7 +70,7 @@ namespace Microsoft.Xna.Framework
// //
// Android events forwarded to GameRunner: // Android events forwarded to GameRunner:
// onPause, onResume, onDestroy, onTouchEvent // onPause, onWindowFocusChanged, onDestroy, onTouchEvent, onBackPressed
// //
protected override void onPause() protected override void onPause()
@ -45,11 +79,39 @@ namespace Microsoft.Xna.Framework
base.onPause(); base.onPause();
} }
protected override void onResume() public override void onWindowFocusChanged(bool hasFocus)
{
base.onWindowFocusChanged(hasFocus);
if (hasFocus)
{
if (immersiveMode)
{
getWindow().getDecorView().setSystemUiVisibility(
android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| android.view.View.SYSTEM_UI_FLAG_FULLSCREEN
| android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
if (object.ReferenceEquals(gameRunner, null))
{
new java.lang.Thread(gameRunner = new GameRunner(this)).start();
}
else
{
gameRunner.ActivityResume();
}
}
}
/*protected override void onResume()
{ {
gameRunner?.ActivityResume(); gameRunner?.ActivityResume();
base.onResume(); base.onResume();
} }*/
protected override void onDestroy() protected override void onDestroy()
{ {
@ -79,33 +141,47 @@ namespace Microsoft.Xna.Framework
return true; return true;
} }
public override void onBackPressed()
{
if (backKeyCode != 0)
gameRunner?.ActivityKey(backKeyCode);
else
base.onBackPressed();
}
// //
// GetMetaAttr // GetMetaAttr_Str, GetMetaAttr_Int
// //
public string GetMetaAttr(string name, bool warn = false) public string GetMetaAttr_Str(string name, string def)
{ {
var info = getPackageManager().getActivityInfo(getComponentName(), name = "BNA." + name;
android.content.pm.PackageManager.GET_ACTIVITIES var str = GetMetaData()?.getString(name);
| android.content.pm.PackageManager.GET_META_DATA);
name = "microsoft.xna.framework." + name;
var str = info?.metaData?.getString(name);
if (string.IsNullOrEmpty(str)) if (string.IsNullOrEmpty(str))
{ {
if (warn) Activity.Log($"missing metadata attribute '{name}'");
Activity.Log($"missing metadata attribute '{name}'"); str = def;
str = null;
} }
return str; return str;
} }
public int GetMetaAttr_Int(string name)
=> GetMetaData()?.getInt("BNA." + name) ?? 0;
private android.os.Bundle GetMetaData()
=> getPackageManager().getActivityInfo(
getComponentName(),
android.content.pm.PackageManager.GET_ACTIVITIES
| android.content.pm.PackageManager.GET_META_DATA)
?.metaData;
// //
// Log // Log
// //
public static void Log(string s) => android.util.Log.i(_LogTag, s); public static void Log(string s) => android.util.Log.i(logTag, s);
private static string _LogTag = "BNA_Game"; private static string logTag;
// //
// data // data
@ -113,6 +189,8 @@ namespace Microsoft.Xna.Framework
private GameRunner gameRunner; private GameRunner gameRunner;
private bool restartActivity; private bool restartActivity;
private bool immersiveMode;
private int backKeyCode;
} }

13
BNA/src/Debug.cs Normal file
View File

@ -0,0 +1,13 @@
namespace System.Diagnostics
{
public static class Debug
{
public static void WriteLine(string message)
{
Microsoft.Xna.Framework.GameRunner.Log(message);
}
}
}

View File

@ -67,7 +67,7 @@ namespace Microsoft.Xna.Framework.Graphics
public string CreateProgram2(string vertexText, string fragmentText) public string CreateProgram2(string vertexText, string fragmentText)
{ {
string errText = null; string errText = null;
Renderer.Get(GraphicsDevice.GLDevice).Send( () => Renderer.Get(GraphicsDevice.GLDevice).Send(true, () =>
{ {
(vertexId, errText) = CompileShader( (vertexId, errText) = CompileShader(
GLES20.GL_VERTEX_SHADER, "vertex", vertexText); GLES20.GL_VERTEX_SHADER, "vertex", vertexText);
@ -93,7 +93,6 @@ namespace Microsoft.Xna.Framework.Graphics
(int, string) CompileShader(int kind, string errKind, string text) (int, string) CompileShader(int kind, string errKind, string text)
{ {
//GameRunner.Log($"SHADER PROGRAM: [[[" + text + "]]]");
string errText = null; string errText = null;
int shaderId = GLES20.glCreateShader(kind); int shaderId = GLES20.glCreateShader(kind);
int errCode = GLES20.glGetError(); int errCode = GLES20.glGetError();
@ -237,7 +236,7 @@ namespace Microsoft.Xna.Framework.Graphics
public (string name, int type, int size)[] GetProgramUniforms() public (string name, int type, int size)[] GetProgramUniforms()
{ {
(string, int, int)[] result = null; (string, int, int)[] result = null;
Renderer.Get(GraphicsDevice.GLDevice).Send( () => Renderer.Get(GraphicsDevice.GLDevice).Send(true, () =>
{ {
var count = new int[1]; var count = new int[1];
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, count, 0); GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, count, 0);
@ -272,7 +271,7 @@ namespace Microsoft.Xna.Framework.Graphics
public void INTERNAL_applyEffect(uint pass) public void INTERNAL_applyEffect(uint pass)
{ {
var graphicsDevice = GraphicsDevice; var graphicsDevice = GraphicsDevice;
Renderer.Get(graphicsDevice.GLDevice).Send( () => Renderer.Get(graphicsDevice.GLDevice).Send(false, () =>
{ {
GLES20.glUseProgram(programId); GLES20.glUseProgram(programId);
int n = Parameters.Count; int n = Parameters.Count;
@ -297,7 +296,7 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
if (! base.IsDisposed) if (! base.IsDisposed)
{ {
Renderer.Get(GraphicsDevice.GLDevice).Send( () => Renderer.Get(GraphicsDevice.GLDevice).Send(true, () =>
{ {
GLES20.glDeleteShader(fragmentId); GLES20.glDeleteShader(fragmentId);
GLES20.glDeleteShader(vertexId); GLES20.glDeleteShader(vertexId);

View File

@ -40,7 +40,7 @@ namespace Microsoft.Xna.Framework.Graphics
} }
} }
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
GLES20.glViewport(v.x, v.y, v.w, v.h); GLES20.glViewport(v.x, v.y, v.w, v.h);
GLES20.glDepthRangef(v.minDepth, v.maxDepth); GLES20.glDepthRangef(v.minDepth, v.maxDepth);
@ -54,7 +54,7 @@ namespace Microsoft.Xna.Framework.Graphics
public static void FNA3D_SetScissorRect(IntPtr device, ref Rectangle scissor) public static void FNA3D_SetScissorRect(IntPtr device, ref Rectangle scissor)
{ {
var s = scissor; var s = scissor;
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
GLES20.glScissor(s.X, s.Y, s.Width, s.Height); GLES20.glScissor(s.X, s.Y, s.Width, s.Height);
}); });
@ -69,7 +69,7 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
var clearColor = color; var clearColor = color;
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(false, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
var WriteMask = state.WriteMask; var WriteMask = state.WriteMask;
@ -180,8 +180,7 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
} }
var renderer = Renderer.Get(device); Renderer.Get(device).Present();
renderer.Present();
} }
// //
@ -192,7 +191,7 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
var input = blendState; var input = blendState;
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
@ -323,7 +322,6 @@ namespace Microsoft.Xna.Framework.Graphics
public static void FNA3D_SetDepthStencilState(IntPtr device, public static void FNA3D_SetDepthStencilState(IntPtr device,
ref FNA3D_DepthStencilState depthStencilState) ref FNA3D_DepthStencilState depthStencilState)
{ {
// AndroidActivity.LogI(">>>>> SET DEPTH AND STENCIL STATE");
} }
// //
@ -335,7 +333,7 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
var input = rasterizerState; var input = rasterizerState;
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
@ -382,6 +380,30 @@ namespace Microsoft.Xna.Framework.Graphics
}); });
} }
//
// FNA3D_GetBackbufferSize
//
public static void FNA3D_GetBackbufferSize(IntPtr device, out int w, out int h)
{
var bounds = GameRunner.Singleton.ClientBounds;
w = bounds.Width;
h = bounds.Height;
}
//
// FNA3D_GetBackbufferSurfaceFormat
//
public static SurfaceFormat FNA3D_GetBackbufferSurfaceFormat(IntPtr device)
{
return SurfaceFormat.Color;
}
//
// FNA3D_GetMaxMultiSampleCount
//
public static int FNA3D_GetMaxMultiSampleCount(IntPtr device, SurfaceFormat format, public static int FNA3D_GetMaxMultiSampleCount(IntPtr device, SurfaceFormat format,
int preferredMultiSampleCount) int preferredMultiSampleCount)
{ {
@ -426,7 +448,7 @@ namespace Microsoft.Xna.Framework.Graphics
int indexOffset = startIndex * elementSize; int indexOffset = startIndex * elementSize;
primitiveCount = PrimitiveCount(primitiveType, primitiveCount); primitiveCount = PrimitiveCount(primitiveType, primitiveCount);
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, (int) indices); GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, (int) indices);
GLES30.glDrawRangeElements(drawMode, minVertexIndex, maxVertexIndex, GLES30.glDrawRangeElements(drawMode, minVertexIndex, maxVertexIndex,
@ -464,7 +486,7 @@ namespace Microsoft.Xna.Framework.Graphics
int drawMode = PrimitiveTypeToDrawMode[(int) primitiveType]; int drawMode = PrimitiveTypeToDrawMode[(int) primitiveType];
primitiveCount = PrimitiveCount(primitiveType, primitiveCount); primitiveCount = PrimitiveCount(primitiveType, primitiveCount);
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
GLES20.glDrawArrays(drawMode, vertexStart, primitiveCount); GLES20.glDrawArrays(drawMode, vertexStart, primitiveCount);
}); });

View File

@ -30,7 +30,7 @@ namespace Microsoft.Xna.Framework.Graphics
private static int CreateBuffer(Renderer renderer, int target, byte dynamic, int size) private static int CreateBuffer(Renderer renderer, int target, byte dynamic, int size)
{ {
int bufferId = 0; int bufferId = 0;
renderer.Send( () => renderer.Send(true, () =>
{ {
int[] id = new int[1]; int[] id = new int[1];
GLES20.glGenBuffers(1, id, 0); GLES20.glGenBuffers(1, id, 0);
@ -74,7 +74,7 @@ namespace Microsoft.Xna.Framework.Graphics
public static void FNA3D_AddDisposeVertexBuffer(IntPtr device, IntPtr buffer) public static void FNA3D_AddDisposeVertexBuffer(IntPtr device, IntPtr buffer)
{ {
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(true, () =>
{ {
GLES20.glDeleteBuffers(1, new int[] { (int) buffer }, 0); GLES20.glDeleteBuffers(1, new int[] { (int) buffer }, 0);
@ -102,7 +102,7 @@ namespace Microsoft.Xna.Framework.Graphics
var dataBuffer = BufferSerializer.Convert( var dataBuffer = BufferSerializer.Convert(
dataPointer, dataLength, state, bufferId); dataPointer, dataLength, state, bufferId);
renderer.Send( () => renderer.Send(false, () =>
{ {
GLES20.glBindBuffer(target, bufferId); GLES20.glBindBuffer(target, bufferId);
@ -151,11 +151,18 @@ namespace Microsoft.Xna.Framework.Graphics
int numBindings, byte bindingsUpdated, int numBindings, byte bindingsUpdated,
int baseVertex) int baseVertex)
{ {
/* skip if FNA says the bindings have not been updated
if (baseVertex == ((State) renderer.UserData).BaseVertex.getAndSet(baseVertex))
{
if (bindingsUpdated == 0)
return;
}*/
var bindingsCopy = new FNA3D_VertexBufferBinding[numBindings]; var bindingsCopy = new FNA3D_VertexBufferBinding[numBindings];
for (int i = 0; i < numBindings; i++) for (int i = 0; i < numBindings; i++)
bindingsCopy[i] = bindings[i]; bindingsCopy[i] = bindings[i];
Renderer.Get(device).Send( () => Renderer.Get(device).Send(false, () =>
{ {
int nextAttribIndex = 0; int nextAttribIndex = 0;
foreach (var binding in bindingsCopy) foreach (var binding in bindingsCopy)
@ -278,8 +285,8 @@ namespace Microsoft.Xna.Framework.Graphics
// we use GCHandle::FromIntPtr to convert that address to an object reference. // we use GCHandle::FromIntPtr to convert that address to an object reference.
// see also: system.runtime.interopservices.GCHandle struct in baselib. // see also: system.runtime.interopservices.GCHandle struct in baselib.
int offset = (int) data; int offset = (int) data;
newBuffer = Convert(GCHandle.FromIntPtr(data - offset).Target, newBuffer = Convert2(GCHandle.FromIntPtr(data - offset).Target,
offset, length, oldBuffer); offset, length, oldBuffer);
if (newBuffer != oldBuffer) if (newBuffer != oldBuffer)
{ {
@ -293,8 +300,8 @@ namespace Microsoft.Xna.Framework.Graphics
} }
public static java.nio.Buffer Convert(object data, int offset, int length, private static java.nio.Buffer Convert2(object data, int offset, int length,
java.nio.Buffer buffer) java.nio.Buffer buffer)
{ {
if (data is short[]) if (data is short[])
{ {
@ -491,6 +498,7 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
public Dictionary<int, int[]> BufferSizeUsage = new Dictionary<int, int[]>(); public Dictionary<int, int[]> BufferSizeUsage = new Dictionary<int, int[]>();
public Dictionary<int, java.nio.Buffer> BufferCache = new Dictionary<int, java.nio.Buffer>(); public Dictionary<int, java.nio.Buffer> BufferCache = new Dictionary<int, java.nio.Buffer>();
//public java.util.concurrent.atomic.AtomicInteger BaseVertex = new java.util.concurrent.atomic.AtomicInteger();
} }
} }

View File

@ -27,20 +27,23 @@ namespace Microsoft.Xna.Framework.Graphics
default: throw new ArgumentException("depthStencilFormat"); default: throw new ArgumentException("depthStencilFormat");
} }
int swapInterval = presentationParameters.presentationInterval switch
{
PresentInterval.Two => 2,
PresentInterval.Immediate => 0,
// Default, One, or any other value:
_ => 1
};
bool checkErrors = GameRunner.Singleton.CheckGlErrors();
var device = Renderer.Create(GameRunner.Singleton.Activity, var device = Renderer.Create(GameRunner.Singleton.Activity,
GameRunner.Singleton.OnSurfaceChanged, GameRunner.Singleton.OnSurfaceChanged,
8, 8, 8, 0, depthSize, stencilSize); 8, 8, 8, 0, depthSize, stencilSize,
swapInterval, checkErrors);
FNA3D_ResetBackbuffer(device, ref presentationParameters); FNA3D_ResetBackbuffer(device, ref presentationParameters);
return device; 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)
};*/
} }
// //

View File

@ -27,7 +27,7 @@ namespace Microsoft.Xna.Framework.Graphics
renderTargetsCopy[i] = renderTargets[i]; renderTargetsCopy[i] = renderTargets[i];
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(false, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
if (state.TargetFramebuffer == 0) if (state.TargetFramebuffer == 0)
@ -99,7 +99,7 @@ namespace Microsoft.Xna.Framework.Graphics
throw new PlatformNotSupportedException(); throw new PlatformNotSupportedException();
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(false, () =>
{ {
GLES20.glBindFramebuffer(GLES30.GL_DRAW_FRAMEBUFFER, 0); GLES20.glBindFramebuffer(GLES30.GL_DRAW_FRAMEBUFFER, 0);
@ -132,7 +132,7 @@ namespace Microsoft.Xna.Framework.Graphics
int texture = (int) renderTarget.texture; int texture = (int) renderTarget.texture;
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(false, () =>
{ {
int textureUnit = GLES20.GL_TEXTURE0 + renderer.TextureUnits - 1; int textureUnit = GLES20.GL_TEXTURE0 + renderer.TextureUnits - 1;
GLES20.glActiveTexture(textureUnit); GLES20.glActiveTexture(textureUnit);
@ -165,7 +165,7 @@ namespace Microsoft.Xna.Framework.Graphics
int bufferId = 0; int bufferId = 0;
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(true, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
@ -192,7 +192,7 @@ namespace Microsoft.Xna.Framework.Graphics
public static void FNA3D_AddDisposeRenderbuffer(IntPtr device, IntPtr renderbuffer) public static void FNA3D_AddDisposeRenderbuffer(IntPtr device, IntPtr renderbuffer)
{ {
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(true, () =>
{ {
GLES20.glDeleteRenderbuffers(1, new int[] { (int) renderbuffer }, 0); GLES20.glDeleteRenderbuffers(1, new int[] { (int) renderbuffer }, 0);
}); });
@ -216,6 +216,8 @@ namespace Microsoft.Xna.Framework.Graphics
int x, int y, int w, int h, int level, int x, int y, int w, int h, int level,
object dataObject, int dataOffset, int dataLength) object dataObject, int dataOffset, int dataLength)
{ {
int[] tempIntArray = null;
java.nio.Buffer buffer = dataObject switch java.nio.Buffer buffer = dataObject switch
{ {
sbyte[] byteArray => sbyte[] byteArray =>
@ -224,10 +226,18 @@ namespace Microsoft.Xna.Framework.Graphics
int[] intArray => int[] intArray =>
java.nio.IntBuffer.wrap(intArray, dataOffset / 4, dataLength / 4), java.nio.IntBuffer.wrap(intArray, dataOffset / 4, dataLength / 4),
Color[] _ =>
// GameRunner constructor sets the marshal size of Color to -1,
// so we expect only negative or zero values here
java.nio.IntBuffer.wrap(
tempIntArray = new int[dataLength <= 0
? (dataLength = -dataLength)
: throw new ArgumentException()]),
_ => throw new ArgumentException(dataObject?.GetType().ToString()), _ => throw new ArgumentException(dataObject?.GetType().ToString()),
}; };
renderer.Send( () => renderer.Send(true, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
if (state.SourceFramebuffer == 0) if (state.SourceFramebuffer == 0)
@ -257,6 +267,16 @@ namespace Microsoft.Xna.Framework.Graphics
GLES20.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER, 0); GLES20.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER, 0);
}); });
if (tempIntArray != null)
{
if (dataOffset > 0)
throw new ArgumentException();
// convert int[] array from the GL call to a Color[] array
var colorArray = (Color[]) dataObject;
for (int i = 0; i < dataLength; i++)
colorArray[i - dataOffset].PackedValue = (uint) tempIntArray[i];
}
} }
// //

View File

@ -90,7 +90,7 @@ namespace Microsoft.Xna.Framework.Graphics
} }
int textureId = 0; int textureId = 0;
renderer.Send( () => renderer.Send(true, () =>
{ {
int id = CreateTexture(renderer, GLES20.GL_TEXTURE_2D, format, levelCount); int id = CreateTexture(renderer, GLES20.GL_TEXTURE_2D, format, levelCount);
if (id != 0) if (id != 0)
@ -138,7 +138,7 @@ namespace Microsoft.Xna.Framework.Graphics
public static void FNA3D_AddDisposeTexture(IntPtr device, IntPtr texture) public static void FNA3D_AddDisposeTexture(IntPtr device, IntPtr texture)
{ {
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(true, () =>
{ {
GLES20.glDeleteTextures(1, new int[] { (int) texture }, 0); GLES20.glDeleteTextures(1, new int[] { (int) texture }, 0);
@ -165,10 +165,13 @@ namespace Microsoft.Xna.Framework.Graphics
int[] intArray => int[] intArray =>
java.nio.IntBuffer.wrap(intArray, dataOffset / 4, dataLength / 4), java.nio.IntBuffer.wrap(intArray, dataOffset / 4, dataLength / 4),
Color[] colorArray =>
bufferFromColorArray(colorArray, dataOffset, dataLength),
_ => throw new ArgumentException(dataObject?.GetType().ToString()), _ => throw new ArgumentException(dataObject?.GetType().ToString()),
}; };
renderer.Send( () => renderer.Send(false, () =>
{ {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
@ -200,6 +203,20 @@ namespace Microsoft.Xna.Framework.Graphics
} }
}); });
java.nio.Buffer bufferFromColorArray(Color[] array, int offset, int length)
{
// GameRunner constructor sets the marshal size of Color to -1,
// so we expect only negative or zero values here
if (offset > 0 || length > 0)
throw new ArgumentException();
// convert Color[] array into an int[] array for the GL call
length = -length;
var intArray = new int[length];
for (int i = 0; i < length; i++)
intArray[i] = (int) array[i - offset].PackedValue;
return java.nio.IntBuffer.wrap(intArray);
}
} }
public static void FNA3D_SetTextureData2D(IntPtr device, IntPtr texture, public static void FNA3D_SetTextureData2D(IntPtr device, IntPtr texture,
@ -344,7 +361,7 @@ namespace Microsoft.Xna.Framework.Graphics
int textureId = (int) texture; int textureId = (int) texture;
var renderer = Renderer.Get(device); var renderer = Renderer.Get(device);
renderer.Send( () => renderer.Send(false, () =>
{ {
var state = (State) renderer.UserData; var state = (State) renderer.UserData;
var config = state.TextureConfigs[textureId]; var config = state.TextureConfigs[textureId];

View File

@ -14,8 +14,6 @@ namespace Microsoft.Xna.Framework
private Activity activity; private Activity activity;
private System.Collections.Hashtable dict; private System.Collections.Hashtable dict;
private int clientWidth, clientHeight;
private Rectangle clientBounds;
private bool recreateActivity; private bool recreateActivity;
private java.util.concurrent.atomic.AtomicInteger inModal; private java.util.concurrent.atomic.AtomicInteger inModal;
@ -31,6 +29,9 @@ namespace Microsoft.Xna.Framework
private const int CONFIG_EVENT = 1; private const int CONFIG_EVENT = 1;
private const int TOUCH_EVENT = 2; private const int TOUCH_EVENT = 2;
private const int KEY_EVENT = 4;
private int eventKeyCode;
// //
// constructor // constructor
@ -40,7 +41,15 @@ namespace Microsoft.Xna.Framework
{ {
this.activity = activity; this.activity = activity;
UpdateConfiguration(false); // in Bluebonnet, the following method is used to specify the
// size that Marshal.SizeOf should return for non-primitive types.
// this is used to enable Texture2D.GetData/SetData to accept
// Color[] arrays. see also SetTextureData in FNA3D_Tex
System.Runtime.InteropServices.Marshal.SetComObjectData(
typeof(System.Runtime.InteropServices.Marshal),
typeof(Color), -1);
UpdateConfiguration();
inModal = new java.util.concurrent.atomic.AtomicInteger(); inModal = new java.util.concurrent.atomic.AtomicInteger();
shouldPause = new java.util.concurrent.atomic.AtomicInteger(); shouldPause = new java.util.concurrent.atomic.AtomicInteger();
@ -71,6 +80,12 @@ namespace Microsoft.Xna.Framework
set => inModal.set(value ? 1 : 0); set => inModal.set(value ? 1 : 0);
} }
//
// CheckGlErrors
//
public bool CheckGlErrors() => activity.GetMetaAttr_Int("check.gl.errors") != 0;
// //
// Thread run() method // Thread run() method
// //
@ -103,6 +118,8 @@ namespace Microsoft.Xna.Framework
GameRunner.Log("========================================"); GameRunner.Log("========================================");
GameRunner.Log(e.ToString()); GameRunner.Log(e.ToString());
GameRunner.Log("========================================"); GameRunner.Log("========================================");
if (! object.ReferenceEquals(e.InnerException, null))
e = e.InnerException;
System.Windows.Forms.MessageBox.Show(e.ToString()); System.Windows.Forms.MessageBox.Show(e.ToString());
} }
@ -111,19 +128,13 @@ namespace Microsoft.Xna.Framework
{ {
Type clsType = null; Type clsType = null;
var clsName = activity.GetMetaAttr("main.class", true); var clsName = activity.GetMetaAttr_Str("main.class", ".Program");
if (clsName != null) if (clsName[0] == '.')
{ clsName = activity.getPackageName() + clsName;
if (clsName[0] == '.') clsType = System.Type.GetType(clsName, false, true);
clsName = activity.getPackageName() + clsName;
clsType = System.Type.GetType(clsName, false, true);
}
if (clsType == null) if (clsType == null)
{
throw new Exception($"main class '{clsName}' not found"); throw new Exception($"main class '{clsName}' not found");
}
return clsType; return clsType;
} }
@ -131,7 +142,7 @@ namespace Microsoft.Xna.Framework
void CallMainMethod(Type mainClass) void CallMainMethod(Type mainClass)
{ {
var method = mainClass.GetMethod("Main"); var method = mainClass.GetMethod("Main") ?? mainClass.GetMethod("main");
if (method.IsStatic) if (method.IsStatic)
{ {
method.Invoke(null, new object[method.GetParameters().Length]); method.Invoke(null, new object[method.GetParameters().Length]);
@ -150,6 +161,7 @@ namespace Microsoft.Xna.Framework
public void MainLoop(Game game) public void MainLoop(Game game)
{ {
int pauseCount = 0; int pauseCount = 0;
bool clearKeys = false;
while (game.RunApplication) while (game.RunApplication)
{ {
@ -197,12 +209,19 @@ namespace Microsoft.Xna.Framework
eventBits = shouldEvents.get(); eventBits = shouldEvents.get();
if ((eventBits & CONFIG_EVENT) != 0) if ((eventBits & CONFIG_EVENT) != 0)
UpdateConfiguration(true); UpdateConfiguration();
if ((eventBits & TOUCH_EVENT) != 0) if ((eventBits & TOUCH_EVENT) != 0)
{ {
Microsoft.Xna.Framework.Input.Mouse Microsoft.Xna.Framework.Input.Mouse
.HandleEvents(clientWidth, clientHeight); .HandleEvents((int) dict["width"], (int) dict["height"]);
}
if ((eventBits & KEY_EVENT) != 0)
{
Microsoft.Xna.Framework.Input.Keyboard.keys.Add(
(Microsoft.Xna.Framework.Input.Keys) eventKeyCode);
clearKeys = true;
} }
} }
@ -211,6 +230,16 @@ namespace Microsoft.Xna.Framework
// //
game.Tick(); game.Tick();
//
// a simulated key is signalled during a single frame
//
if (clearKeys)
{
clearKeys = false;
Microsoft.Xna.Framework.Input.Keyboard.keys.Clear();
}
} }
InModal = true; InModal = true;
@ -263,12 +292,18 @@ namespace Microsoft.Xna.Framework
public void ActivityPause() public void ActivityPause()
{ {
if (! InModal) if (shouldResume.get() == 0)
{ {
shouldPause.incrementAndGet(); Microsoft.Xna.Framework.Media.MediaPlayer.ActivityPauseOrResume(true);
waitForPause.block(); Microsoft.Xna.Framework.Audio.SoundEffect.ActivityPauseOrResume(true);
if (shouldExit.get() == 0)
waitForPause.close(); if (! InModal)
{
shouldPause.incrementAndGet();
waitForPause.block();
if (shouldExit.get() == 0)
waitForPause.close();
}
} }
} }
@ -279,7 +314,16 @@ namespace Microsoft.Xna.Framework
public void ActivityResume() public void ActivityResume()
{ {
if (shouldResume.compareAndSet(1, 0)) if (shouldResume.compareAndSet(1, 0))
{
// force a call to UpdateConfiguration after main loop wakes up.
// this will not actually invoke callbacks if nothing has changed.
OnSurfaceChanged();
waitForResume.open(); waitForResume.open();
Microsoft.Xna.Framework.Media.MediaPlayer.ActivityPauseOrResume(false);
Microsoft.Xna.Framework.Audio.SoundEffect.ActivityPauseOrResume(false);
}
} }
// //
@ -308,6 +352,23 @@ namespace Microsoft.Xna.Framework
} }
} }
//
// ActivityKey
//
public void ActivityKey(int keyCode)
{
for (;;)
{
int v = shouldEvents.get();
if (shouldEvents.compareAndSet(v, v | KEY_EVENT))
{
eventKeyCode = keyCode;
break;
}
}
}
// //
// OnSurfaceChanged // OnSurfaceChanged
// //
@ -326,26 +387,76 @@ namespace Microsoft.Xna.Framework
// UpdateConfiguration // UpdateConfiguration
// //
void UpdateConfiguration(bool withCallback) void UpdateConfiguration()
{ {
var metrics = new android.util.DisplayMetrics(); var metrics = GetDisplayMetrics(activity);
activity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics); int width = metrics.widthPixels;
int height = metrics.heightPixels;
clientWidth = metrics.widthPixels; var dict = new System.Collections.Hashtable();
clientHeight = metrics.heightPixels;
clientBounds = new Rectangle(0, 0, clientWidth, clientHeight);
if (dict == null) dict["width"] = width;
dict = new System.Collections.Hashtable(); dict["height"] = height;
dict["dpi"] = (int) ((metrics.xdpi + metrics.ydpi) * 0.5f);
// int dpi - pixels per inch if (object.ReferenceEquals(this.dict, null))
dict["dpi"] = (int) ((metrics.xdpi + metrics.ydpi) * 0.5f);
if (withCallback)
{ {
// on first call, set the dict without invoking callbacks
SetExtra(dict, width, height);
this.dict = dict;
}
else if (! DictsEqual(dict, this.dict))
{
SetExtra(dict, width, height);
this.dict = dict;
OnClientSizeChanged(); OnClientSizeChanged();
OnOrientationChanged(); OnOrientationChanged();
} }
android.util.DisplayMetrics GetDisplayMetrics(android.app.Activity activity)
{
bool isMultiWindow = false;
if (android.os.Build.VERSION.SDK_INT >= 24)
isMultiWindow = activity.isInMultiWindowMode();
var metrics = new android.util.DisplayMetrics();
var display = activity.getWindowManager().getDefaultDisplay();
if (! isMultiWindow)
{
// getRealMetrics gets real size of the entire screen.
// this is what we need in full screen immersive mode.
display.getRealMetrics(metrics);
}
else
{
// getMetrics gets the size of the split window, minus
// window frames. this is useful in multi-window mode.
display.getMetrics(metrics);
}
return metrics;
}
bool DictsEqual(System.Collections.Hashtable dict1,
System.Collections.Hashtable dict2)
{
foreach (var key in dict1.Keys)
{
if (! dict1[key].Equals(dict2[key]))
return false;
}
return true;
}
void SetExtra(System.Collections.Hashtable dict, int width, int height)
{
dict["bounds"] = new Rectangle(0, 0, width, height);
dict["openUri"] = (Action<string>) OpenUri;
}
} }
// //
@ -359,11 +470,32 @@ namespace Microsoft.Xna.Framework
return null; return null;
} }
//
// OpenUri
//
private void OpenUri(string uri)
{
activity.runOnUiThread(((java.lang.Runnable.Delegate) (() =>
{
try
{
activity.startActivity(
new android.content.Intent(android.content.Intent.ACTION_VIEW,
android.net.Uri.parse(uri)));
}
catch (Exception e)
{
GameRunner.Log(e.ToString());
}
})).AsInterface());
}
// //
// GameWindow interface // GameWindow interface
// //
public override Rectangle ClientBounds => clientBounds; public override Rectangle ClientBounds => (Rectangle) dict["bounds"];
public override string ScreenDeviceName => "Android"; public override string ScreenDeviceName => "Android";
@ -374,8 +506,8 @@ namespace Microsoft.Xna.Framework
public override DisplayOrientation CurrentOrientation public override DisplayOrientation CurrentOrientation
{ {
get => (clientWidth < clientHeight) ? DisplayOrientation.Portrait get => ((int) dict["width"] < (int) dict["height"])
: DisplayOrientation.LandscapeLeft; ? DisplayOrientation.Portrait : DisplayOrientation.LandscapeLeft;
set set
{ {
bool portrait = 0 != (value & DisplayOrientation.Portrait); bool portrait = 0 != (value & DisplayOrientation.Portrait);
@ -387,7 +519,7 @@ namespace Microsoft.Xna.Framework
else if (landscape && (! portrait)) else if (landscape && (! portrait))
r = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE; r = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
else else
r = android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_USER; return;
activity.setRequestedOrientation(r); activity.setRequestedOrientation(r);
} }
} }

View File

@ -201,6 +201,12 @@ namespace Microsoft.Xna.Framework.Input
public int Y { get; set; } public int Y { get; set; }
public ButtonState LeftButton { get; set; } public ButtonState LeftButton { get; set; }
} }
[java.attr.Discard] // discard in output
public static class Keyboard
{
public static List<Keys> keys;
}
} }

View File

@ -15,6 +15,7 @@ namespace Microsoft.Xna.Framework.Media
[java.attr.RetainType] private static float volume; [java.attr.RetainType] private static float volume;
[java.attr.RetainType] private static bool muted; [java.attr.RetainType] private static bool muted;
[java.attr.RetainType] private static bool looping; [java.attr.RetainType] private static bool looping;
[java.attr.RetainType] private static bool wasPlayingBeforeActivityPause;
// //
// static constructor // static constructor
@ -283,6 +284,28 @@ namespace Microsoft.Xna.Framework.Media
} }
} }
//
// ActivityPauseOrResume
//
public static void ActivityPauseOrResume(bool pausing)
{
if (pausing)
{
if (State == MediaState.Playing)
{
wasPlayingBeforeActivityPause = true;
Pause();
}
}
else if (wasPlayingBeforeActivityPause)
{
wasPlayingBeforeActivityPause = false;
Resume();
}
}
} }
// //

View File

@ -20,6 +20,8 @@ namespace Microsoft.Xna.Framework.Graphics
private android.os.ConditionVariable waitObject; private android.os.ConditionVariable waitObject;
private java.util.concurrent.atomic.AtomicInteger paused; private java.util.concurrent.atomic.AtomicInteger paused;
private Action actionOnChanged; private Action actionOnChanged;
private int swapInterval;
private bool checkErrors;
public object UserData; public object UserData;
@ -39,11 +41,14 @@ namespace Microsoft.Xna.Framework.Graphics
private Renderer(android.app.Activity activity, Action onChanged, private Renderer(android.app.Activity activity, Action onChanged,
int redSize, int greenSize, int blueSize, int redSize, int greenSize, int blueSize,
int alphaSize, int depthSize, int stencilSize) int alphaSize, int depthSize, int stencilSize,
int swapInterval, bool checkErrors)
{ {
waitObject = new android.os.ConditionVariable(); waitObject = new android.os.ConditionVariable();
paused = new java.util.concurrent.atomic.AtomicInteger(); paused = new java.util.concurrent.atomic.AtomicInteger();
actionOnChanged = onChanged; actionOnChanged = onChanged;
this.swapInterval = swapInterval;
this.checkErrors = checkErrors;
activity.runOnUiThread(((java.lang.Runnable.Delegate) (() => activity.runOnUiThread(((java.lang.Runnable.Delegate) (() =>
{ {
@ -55,7 +60,6 @@ namespace Microsoft.Xna.Framework.Graphics
surface.setRenderer(this); surface.setRenderer(this);
surface.setRenderMode(android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY); surface.setRenderMode(android.opengl.GLSurfaceView.RENDERMODE_WHEN_DIRTY);
activity.setContentView(surface); activity.setContentView(surface);
})).AsInterface()); })).AsInterface());
// wait for one onDrawFrame callback, which tells us that // wait for one onDrawFrame callback, which tells us that
@ -78,16 +82,21 @@ namespace Microsoft.Xna.Framework.Graphics
// Send // Send
// //
public void Send(Action action) public void Send(bool wait, Action action)
{ {
Exception exc = null; Exception exc = null;
if (paused.get() == 0) if (paused.get() == 0)
{ {
var cond = new android.os.ConditionVariable(); if (! waitObject.block(2000))
surface.queueEvent(((java.lang.Runnable.Delegate) (() =>
{ {
var error = GLES20.glGetError(); // see also Present(). a timeout here means that onDrawFrame
if (error == GLES20.GL_NO_ERROR) // was never called, so the surface was probably destroyed.
paused.compareAndSet(0, -1);
}
else
{
var cond = wait ? new android.os.ConditionVariable() : null;
surface.queueEvent(((java.lang.Runnable.Delegate) (() =>
{ {
try try
{ {
@ -97,18 +106,21 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
exc = exc2; exc = exc2;
} }
error = GLES20.glGetError(); if (checkErrors)
} {
if (error != GLES20.GL_NO_ERROR) var error = GLES20.glGetError();
exc = new Exception($"GL Error {error}"); if (error != GLES20.GL_NO_ERROR)
cond.open(); exc = new Exception($"GL Error {error}");
})).AsInterface()); }
cond.block(); if (cond != null)
cond.open();
})).AsInterface());
if (cond != null)
cond.block();
}
} }
if (exc != null) if (exc != null)
{
throw new AggregateException(exc.Message, exc); throw new AggregateException(exc.Message, exc);
}
} }
// //
@ -119,7 +131,19 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
waitObject.close(); waitObject.close();
surface.requestRender(); surface.requestRender();
waitObject.block();
// GLSurfaceView runs queued events before checking if render
// was requested; but if the event queue is empty, it checks
// if render requested, then calls onDrawFrame(). thus the
// sequence of events is as follows:
// - Present() blocks waitObject and requests render
// - any events already queued by Send() are processed by
// GLSurfaceView, before it checks for a requested render.
// - Send() delays any new events until waitObject is opened.
// - OnDrawFrame() is called and opens waitObject, and when
// it returns to GLSurfaceView, the GL buffers are swapped.
// - with waitObject open, Send() queues new events.
} }
// //
@ -132,6 +156,17 @@ namespace Microsoft.Xna.Framework.Graphics
// if onSurfaceCreated is called while resuming from pause, // if onSurfaceCreated is called while resuming from pause,
// it means the GL context was lost // it means the GL context was lost
paused.compareAndSet(1, -1); paused.compareAndSet(1, -1);
// assign high priority to the rendering thread
java.lang.Thread.currentThread()
.setPriority(java.lang.Thread.MAX_PRIORITY);
// set swap interval
if (swapInterval != 1)
{
var eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
EGL14.eglSwapInterval(eglDisplay, swapInterval);
}
} }
[java.attr.RetainName] [java.attr.RetainName]
@ -188,7 +223,8 @@ namespace Microsoft.Xna.Framework.Graphics
public static IntPtr Create(android.app.Activity activity, Action onChanged, public static IntPtr Create(android.app.Activity activity, Action onChanged,
int redSize, int greenSize, int blueSize, int redSize, int greenSize, int blueSize,
int alphaSize, int depthSize, int stencilSize) int alphaSize, int depthSize, int stencilSize,
int swapInterval, bool checkErrors)
{ {
for (;;) for (;;)
{ {
@ -216,7 +252,8 @@ namespace Microsoft.Xna.Framework.Graphics
deviceId = deviceId, deviceId = deviceId,
renderer = new Renderer(activity, onChanged, renderer = new Renderer(activity, onChanged,
redSize, greenSize, blueSize, redSize, greenSize, blueSize,
alphaSize, depthSize, stencilSize), alphaSize, depthSize, stencilSize,
swapInterval, checkErrors),
activity = new java.lang.@ref.WeakReference(activity), activity = new java.lang.@ref.WeakReference(activity),
}); });
@ -290,7 +327,7 @@ namespace Microsoft.Xna.Framework.Graphics
foreach (var renderer in GetRenderersForActivity(activity)) foreach (var renderer in GetRenderersForActivity(activity))
{ {
renderer.surface.onPause(); renderer.surface.onPause();
renderer.paused.set(1); renderer.paused.compareAndSet(0, 1);
} }
} }
@ -304,9 +341,20 @@ namespace Microsoft.Xna.Framework.Graphics
{ {
if (renderer.paused.get() != 0) if (renderer.paused.get() != 0)
{ {
renderer.waitObject.close(); // we cannot use waitObject for resuming, because the surface
// may have been destroyed between pause and resume, in which
// case onDrawFrame would not get called.
var cond = new android.os.ConditionVariable();
renderer.surface.queueEvent(((java.lang.Runnable.Delegate) (
() => cond.open() )).AsInterface());
renderer.surface.onResume(); renderer.surface.onResume();
renderer.waitObject.block(); if (! cond.block(2000))
{
// something is wrong if the queued event did not run
return false;
}
if (! renderer.paused.compareAndSet(1, 0)) if (! renderer.paused.compareAndSet(1, 0))
{ {

View File

@ -264,6 +264,29 @@ namespace Microsoft.Xna.Framework.Audio
public static float DistanceScale { get; set; } public static float DistanceScale { get; set; }
public static float DopplerScale { get; set; } public static float DopplerScale { get; set; }
public static float SpeedOfSound { get; set; } public static float SpeedOfSound { get; set; }
//
// ActivityPauseOrResume
//
public static void ActivityPauseOrResume(bool pausing)
{
try
{
instancesLock.@lock();
int num = instancesList.size();
for (int idx = 0; idx < num; idx++)
{
var instRef = (java.lang.@ref.WeakReference) instancesList.get(idx);
((SoundEffectInstance) instRef.get())?.ActivityPauseOrResume(pausing);
}
}
finally
{
instancesLock.unlock();
}
}
} }
@ -279,6 +302,7 @@ namespace Microsoft.Xna.Framework.Audio
[java.attr.RetainType] private android.media.AudioTrack track; [java.attr.RetainType] private android.media.AudioTrack track;
[java.attr.RetainType] private float pitch, pan, volume; [java.attr.RetainType] private float pitch, pan, volume;
[java.attr.RetainType] private bool isLooped; [java.attr.RetainType] private bool isLooped;
[java.attr.RetainType] private bool wasPlayingBeforeActivityPause;
// //
// Constructor (for SoundEffect.CreateInstance) // Constructor (for SoundEffect.CreateInstance)
@ -595,6 +619,27 @@ namespace Microsoft.Xna.Framework.Audio
public void Apply3D(AudioListener listener, AudioEmitter emitter) { } public void Apply3D(AudioListener listener, AudioEmitter emitter) { }
public void Apply3D(AudioListener[] listeners, AudioEmitter emitter) { } public void Apply3D(AudioListener[] listeners, AudioEmitter emitter) { }
//
// ActivityPauseOrResume
//
public void ActivityPauseOrResume(bool pausing)
{
if (pausing)
{
if (State == SoundState.Playing)
{
wasPlayingBeforeActivityPause = true;
Pause();
}
}
else if (wasPlayingBeforeActivityPause)
{
wasPlayingBeforeActivityPause = false;
Resume();
}
}
} }

View File

@ -7,6 +7,10 @@
package="com.spaceflint.bluebonnet.xnademo1"> package="com.spaceflint.bluebonnet.xnademo1">
<!-- API 18, GLES 3.0 --> <!-- API 18, GLES 3.0 -->
<!-- note that if testing in the Android emulator, you may need to add
the following line in your ~/.android/advancedFeatures.ini file:
GLESDynamicVersion=on
!-->
<uses-sdk android:minSdkVersion="18" android:targetSdkVersion="29" /> <uses-sdk android:minSdkVersion="18" android:targetSdkVersion="29" />
<uses-feature android:glEsVersion="0x00030000" android:required="true" /> <uses-feature android:glEsVersion="0x00030000" android:required="true" />
@ -15,7 +19,10 @@
<application android:label="BNA_Demo1" <application android:label="BNA_Demo1"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:isGame="true" > android:isGame="true"
android:resizeableActivity="true"
android:supportsPictureInPicture="true"
>
<!-- set android:screenOrientation if you need to lock orientation: <!-- set android:screenOrientation if you need to lock orientation:
https://developer.android.com/guide/topics/manifest/activity-element#screen --> https://developer.android.com/guide/topics/manifest/activity-element#screen -->
@ -29,21 +36,38 @@
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
android:immersive="true" android:immersive="true"
android:launchMode="singleTask" android:launchMode="singleTask"
android:maxAspectRatio="9"
> >
<!-- microsoft.xna.framework.log.tag sets the log identifier <!-- the name of the main entrypoint class. if starts with a dot,
for log messages printed via android.util.Log by the app. then it is appended to the namespace specifies in the package
if omitted, the default is 'BNA_Game' --> attribute. the default is '.Program' -->
<meta-data android:name="BNA.main.class" android:value=".Program"/>
<meta-data android:name="microsoft.xna.framework.log.tag" <!-- the log identifier for log messages printed via android.util.Log
android:value="BNA_Demo1"/> by the app. the default is 'BNA_Game' -->
<meta-data android:name="BNA.log.tag" android:value="BNA_Demo1"/>
<!-- microsoft.xna.framework.main.class sets the name of the main <!-- specifies whether to call glGetError() after every GL call.
or entrypoint class. if starts with a dot, it is appended non-zero value enables error checks, zero skips error checks.
to the namespace specifies in the package attribute. --> this has a negative impact on performance, and is not recommended
in production builds. the default is '0' -->
<meta-data android:name="BNA.check.gl.errors" android:value="0"/>
<meta-data android:name="microsoft.xna.framework.main.class" <!-- specifies whether to disable screen timeout, if set to a non-zero
android:value=".Program"/> value. the default is '0' -->
<meta-data android:name="BNA.keep.screen.on" android:value="0"/>
<!-- specifies whether to enable full screen immersive mode, if set
to a non-zero value. the default is '0' -->
<meta-data android:name="BNA.immersive.mode" android:value="0"/>
<!-- specifies the key to signal, for one frame, when the back button
is pressed. if omitted or specified as zero, default handling of
the back button occurs, which typically stops the activity.
Escape = Microsoft.Xna.Framework.Input.Keys.Escape = 27.
Back = Microsoft.Xna.Framework.Input.Keys.Back = 8. -->
<meta-data android:name="BNA.back.key" android:value="0"/>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@ -51,6 +75,15 @@
</intent-filter> </intent-filter>
</activity> </activity>
<meta-data android:name="android.max_aspect" android:value="9" />
</application> </application>
<supports-screens android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true" />
</manifest> </manifest>

View File

@ -24,6 +24,7 @@
APK_TEMP_DIR = directory where APK processing occurs APK_TEMP_DIR = directory where APK processing occurs
e.g. $(OutputDir)/MyGame/Debug/Content e.g. $(OutputDir)/MyGame/Debug/Content
IMPORTANT: this directory will be deleted and recreated!
KEYSTORE_FILE = path to a keystore file used in APK signing KEYSTORE_FILE = path to a keystore file used in APK signing
@ -33,8 +34,6 @@
BLUEBONNET_EXE = path to Bluebonnet.exe program 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 ANDROID_JAR = path to Android.jar file, for desired API level
e.g. $(ANDROID_HOME)/android-28/android.jar e.g. $(ANDROID_HOME)/android-28/android.jar

24
build_bna.bat Normal file
View File

@ -0,0 +1,24 @@
@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 "%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
)
echo ========================================
echo Building BNA. Command:
echo MSBuild BNA -p:Configuration=Release
echo ========================================
MSBuild BNA -p:Configuration=Release
:EOF