diff --git a/DotNet/Android.cs b/DotNet/Android.cs deleted file mode 100644 index 39b2e9f..0000000 --- a/DotNet/Android.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace Android; - -/// -/// Android log priority values, in increasing order of priority. -/// from Android's log.h -/// -enum LogPriority -{ - /// - /// For internal use only. - /// - Unknown = 0, - /// - /// The default priority, for internal use only. - /// - Default, /* only for SetMinPriority() */ - /// - /// Verbose logging. Should typically be disabled for a release apk. - /// - Verbose, - /// - /// Debug logging. Should typically be disabled for a release apk. - /// - Debug, - /// - /// Informational logging. Should typically be disabled for a release apk. - /// - Info, - /// - /// Warning logging. For use with recoverable failures. - /// - Warn, - /// - /// Error logging. For use with unrecoverable failures. - /// - Error, - /// - /// Fatal logging. For use when aborting. - /// - Fatal, - /// - /// For internal use only. - /// - Silent, /* only for SetMinPriority(); must be last */ -} - -static class Log -{ - public const string Tag = "DOTNET"; - - /// - /// Writes a formatted string to the log, with priority prio and tag tag. - /// The details of formatting are the same as for printf(3): http://man7.org/linux/man-pages/man3/printf.3.html - /// https://developer.android.com/ndk/reference/group/logging#__android_log_print - /// - [DllImport("log", EntryPoint = "__android_log_print", CallingConvention = CallingConvention.Cdecl)] - static extern int LogPrint(LogPriority priority, string tag, string message); - - [Conditional("TRACE")] - public static void Info(string message) => - LogPrint(LogPriority.Info, Tag, message); - - [Conditional("TRACE")] - public static void IsNull(string message, object obj) => - Info($"{message}: {obj?.ToString() ?? "NULL!"}"); - - public static void Error(string message) => - LogPrint(LogPriority.Info, Tag, message); - - public static void Exception(string message, Exception exc) => - Error($"{message}: {exc}"); -} \ No newline at end of file diff --git a/DotNet/Class1.cs b/DotNet/Class1.cs new file mode 100644 index 0000000..bd9c6ee --- /dev/null +++ b/DotNet/Class1.cs @@ -0,0 +1,7 @@ +using System.Runtime.InteropServices; + +class Class1 +{ + [UnmanagedCallersOnly(EntryPoint = "ManagedAdd")] + public static int ManagedAdd(int x, int y) => x + y; +} \ No newline at end of file diff --git a/DotNet/GLES20.cs b/DotNet/GLES20.cs deleted file mode 100644 index c2a93e0..0000000 --- a/DotNet/GLES20.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Android; - -/// -/// https://github.com/mono/SkiaSharp/blob/9274aeec807fd17eec2a3266ad4c2475c37d8a0c/source/SkiaSharp.Views/SkiaSharp.Views.Shared/GlesInterop/Gles.cs -/// -public static class GLES20 -{ - const string libGLESv2 = "libGLESv2.so"; - - public const int FRAMEBUFFER_BINDING = 0x8CA6; - public const int RENDERBUFFER_BINDING = 0x8CA7; - - // ClearBufferMask - public const int DEPTH_BUFFER_BIT = 0x00000100; - public const int STENCIL_BUFFER_BIT = 0x00000400; - public const int COLOR_BUFFER_BIT = 0x00004000; - - // GetPName - public const int SUBPIXEL_BITS = 0x0D50; - public const int RED_BITS = 0x0D52; - public const int GREEN_BITS = 0x0D53; - public const int BLUE_BITS = 0x0D54; - public const int ALPHA_BITS = 0x0D55; - public const int DEPTH_BITS = 0x0D56; - public const int STENCIL_BITS = 0x0D57; - public const int SAMPLES = 0x80A9; - - [DllImport(libGLESv2)] - public static extern void glClear(uint mask); - - [DllImport(libGLESv2)] - public static extern void glClearColor(float red, float green, float blue, float alpha); - - [DllImport(libGLESv2)] - public static extern void glGetIntegerv(uint pname, out int data); - - [DllImport(libGLESv2)] - public static extern void glViewport(int x, int y, int width, int height); -} \ No newline at end of file diff --git a/DotNet/Renderer.cs b/DotNet/Renderer.cs deleted file mode 100644 index 2e89218..0000000 --- a/DotNet/Renderer.cs +++ /dev/null @@ -1,145 +0,0 @@ -using Android; -using System.Runtime.InteropServices; -using SkiaSharp; - -namespace libdotnet; - -/// -/// https://github.com/mono/SkiaSharp/blob/9274aeec807fd17eec2a3266ad4c2475c37d8a0c/source/SkiaSharp.Views/SkiaSharp.Views/Platform/Android/SKGLSurfaceViewRenderer.cs#L32 -/// -public class Renderer -{ - const SKColorType colorType = SKColorType.Rgba8888; - const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; - - static GRContext? context; - static GRGlFramebufferInfo glInfo; - static GRBackendRenderTarget? renderTarget; - static SKSurface? surface; - static SKCanvas? canvas; - static SKSizeI lastSize; - static SKSizeI newSize; - - static readonly SKFrameCounter frameCounter = new(); - static readonly SKPaint currentPaint = new(); - static SKRuntimeShaderBuilder? currentEffectBuilder; - static SKShader? currentShader; - static float X, Y; - - [UnmanagedCallersOnly(EntryPoint = "Render")] - public static void Render() - { - Log.Info("Render()"); - try - { - GLES20.glClear(GLES20.COLOR_BUFFER_BIT | GLES20.DEPTH_BUFFER_BIT | GLES20.STENCIL_BUFFER_BIT); - - // create the contexts if not done already - if (context == null) - { - var glInterface = GRGlInterface.Create(); - Log.IsNull("Created GRGlInterface", glInterface); - context = GRContext.CreateGl(glInterface); - Log.IsNull("Created GRContext", context); - } - - // manage the drawing surface - if (renderTarget == null || lastSize != newSize || !renderTarget.IsValid) - { - // create or update the dimensions - lastSize = newSize; - - // read the info from the buffer - GLES20.glGetIntegerv(GLES20.FRAMEBUFFER_BINDING, out int frame); - GLES20.glGetIntegerv(GLES20.STENCIL_BITS, out int stencil); - GLES20.glGetIntegerv(GLES20.SAMPLES, out int samples); - var maxSamples = context.GetMaxSurfaceSampleCount(colorType); - if (samples > maxSamples) - samples = maxSamples; - glInfo = new GRGlFramebufferInfo((uint)frame, colorType.ToGlSizedFormat()); - - // destroy the old surface - surface?.Dispose(); - surface = null; - canvas = null; - - // re-create the render target - renderTarget?.Dispose(); - renderTarget = new GRBackendRenderTarget(newSize.Width, newSize.Height, samples, stencil, glInfo); - Log.IsNull("Created GRBackendRenderTarget", renderTarget); - } - - // create the surface - if (surface == null) - { - Log.IsNull("Creating SKSurface, context", context); - Log.IsNull("Creating SKSurface, renderTarget", renderTarget); - surface = SKSurface.Create(context, renderTarget, surfaceOrigin, colorType); - Log.IsNull("Created SKSurface", surface); - canvas = surface.Canvas; - Log.IsNull("surface.Canvas", canvas); - } - - ArgumentNullException.ThrowIfNull(canvas); - using (new SKAutoCanvasRestore(canvas, true)) - { - frameCounter.NextFrame(); - - currentEffectBuilder ??= Shaders.GetRandomShader(); - currentEffectBuilder.Uniforms["iResolution"] = new SKPoint3(lastSize.Width, lastSize.Height, 0); - currentEffectBuilder.Uniforms["iTime"] = (float)frameCounter.TotalDuration.TotalSeconds; - currentEffectBuilder.Uniforms["iMouse"] = new SKPoint(X, Y); - currentShader = currentEffectBuilder.Build(); - currentPaint.Shader = currentShader; - - canvas.DrawRect(0, 0, lastSize.Width, lastSize.Height, currentPaint); - } - - // flush the SkiaSharp contents to GL - canvas.Flush(); - context.Flush(); - } - catch (Exception exc) - { - Log.Exception("Exception in Render()", exc); - } - } - - [UnmanagedCallersOnly(EntryPoint = "Resize")] - public static void Resize(int width, int height) - { - Log.Info($"Resize({width}, {height})"); - try - { - GLES20.glViewport(0, 0, width, height); - - // get the new surface size - newSize = new SKSizeI(width, height); - } - catch (Exception exc) - { - Log.Exception("Exception in Resize()", exc); - } - } - - /// - /// Called from C++ on input - /// - [UnmanagedCallersOnly(EntryPoint = "OnTap")] - public static void OnTap(float x, float y) - { - Log.Info($"OnTap({x}, {y})"); - try - { - X = x; - Y = y; - - // Should trigger a new random shader - currentEffectBuilder = null; - } - catch (Exception exc) - { - Log.Exception("Exception in OnTap()", exc); - } - } -} diff --git a/DotNet/SKFrameCounter.cs b/DotNet/SKFrameCounter.cs deleted file mode 100644 index 699abdd..0000000 --- a/DotNet/SKFrameCounter.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace SkiaSharp; - -/// -/// From: https://github.com/mono/SkiaSharp.Extended/blob/93efd2d38783d6d8f638623327e7f9749cea70e2/source/SkiaSharp.Extended.UI/Utils/SKFrameCounter.shared.cs -/// -class SKFrameCounter -{ - private bool firstRender = true; - private int lastTick; - - public SKFrameCounter() - { - Reset(); - } - - public TimeSpan Duration { get; private set; } - - public TimeSpan TotalDuration { get; private set; } - - public void Reset() - { - firstRender = true; - - Duration = TimeSpan.Zero; - } - - public TimeSpan NextFrame() - { - if (firstRender) - { - lastTick = Environment.TickCount; - firstRender = false; - } - - var ticks = Environment.TickCount; - var delta = ticks - lastTick; - lastTick = ticks; - - Duration = TimeSpan.FromMilliseconds(delta); - TotalDuration += Duration; - return Duration; - } -} \ No newline at end of file diff --git a/DotNet/Shaders.cs b/DotNet/Shaders.cs deleted file mode 100644 index 54f92b7..0000000 --- a/DotNet/Shaders.cs +++ /dev/null @@ -1,141 +0,0 @@ -using Android; -using SkiaSharp; - -namespace libdotnet; - -static class Shaders -{ - public static SKRuntimeShaderBuilder GetRandomShader() - { - string source = - """ - uniform float3 iResolution; // Viewport resolution (pixels) - uniform float iTime; // Shader playback time (s) - uniform float2 iMouse; // Mouse drag pos=.xy - - """; - source += GetRandomShader(Random.Shared.Next(3)); - return SKRuntimeEffect.BuildShader(source); - } - - static string GetRandomShader(int index) - { - Log.Info($"GetRandomShader, index: {index}"); - - return index switch - { - // https://shaders.skia.org/?id=de2a4d7d893a7251eb33129ddf9d76ea517901cec960db116a1bbd7832757c1f - // Source: @notargs https://twitter.com/notargs/status/1250468645030858753 - 0 => """ - float f(vec3 p) { - p.z -= iTime * 10.; - float a = p.z * .1; - p.xy *= mat2(cos(a), sin(a), -sin(a), cos(a)); - return .1 - length(cos(p.xy) + sin(p.yz)); - } - - half4 main(vec2 fragcoord) { - vec3 d = .5 - fragcoord.xy1 / iResolution.y; - vec3 p=vec3(0); - for (int i = 0; i < 32; i++) { - p += f(p) * d; - } - return ((sin(p) + vec3(2, 5, 12)) / length(p)).xyz1; - } - """, - - // https://shaders.skia.org/?id=80c3854680c3e99d71fbe24d86185d5bb20cb047305242f9ecb5aff0f102cf73 - // Source: @kamoshika_vrc https://twitter.com/kamoshika_vrc/status/1495081980278751234 - 1 => """ - const float PI2 = 6.28318530718; - float F(vec2 c){ - return fract(sin(dot(c, vec2(12.9898, 78.233))) * 43758.5453); - } - - half4 main(float2 FC) { - vec4 o; - float t = iTime; - vec2 r = iResolution.xy * vec2(1, -1); - vec3 R=normalize(vec3((FC.xy*2.-r)/r.y,1)); - for(float i=0; i<100; ++i) { - float I=floor(t/.1)+i; - float d=(I*.1-t)/R.z; - vec2 p=d*R.xy+vec2(sin(t+F(I.xx)*PI2)*.3+F(I.xx*.9),t+F(I.xx*.8)); - if (F(I/100+ceil(p))<.2) { - o+=smoothstep(.1,0.,length(fract(p)-.5))*exp(-d*d*.04); - } - } - return o; - } - """, - - // https://shaders.skia.org/?id=e0ec9ef204763445036d8a157b1b5c8929829c3e1ee0a265ed984aeddc8929e2 - // Star Nest by Pablo Roman Andrioli - // This content is under the MIT License. - 2 => """ - const int iterations = 17; - const float formuparam = 0.53; - - const int volsteps = 10; - const float stepsize = 0.1; - - const float zoom = 0.800; - const float tile = 0.850; - const float speed =0.010 ; - - const float brightness =0.0015; - const float darkmatter =0.300; - const float distfading =0.730; - const float saturation =0.850; - - - half4 main( in vec2 fragCoord ) - { - //get coords and direction - vec2 uv=fragCoord.xy/iResolution.xy-.5; - uv.y*=iResolution.y/iResolution.x; - vec3 dir=vec3(uv*zoom,1.); - float time=iTime*speed+.25; - - //mouse rotation - float a1=.5+iMouse.x/iResolution.x*2.; - float a2=.8+iMouse.y/iResolution.y*2.; - mat2 rot1=mat2(cos(a1),sin(a1),-sin(a1),cos(a1)); - mat2 rot2=mat2(cos(a2),sin(a2),-sin(a2),cos(a2)); - dir.xz*=rot1; - dir.xy*=rot2; - vec3 from=vec3(1.,.5,0.5); - from+=vec3(time*2.,time,-2.); - from.xz*=rot1; - from.xy*=rot2; - - //volumetric rendering - float s=0.1,fade=1.; - vec3 v=vec3(0.); - for (int r=0; r6) fade*=1.-dm; // dark matter, don't render near - //v+=vec3(dm,dm*.5,0.); - v+=fade; - v+=vec3(s,s*s,s*s*s*s)*a*brightness*fade; // coloring based on distance - fade*=distfading; // distance fading - s+=stepsize; - } - v=mix(vec3(length(v)),v,saturation); //color adjust - return vec4(v*.01,1.); - } - """, - - _ => throw new IndexOutOfRangeException($"No shader for index {index}"), - }; - } -} \ No newline at end of file diff --git a/Native/app/src/main/cpp/CMakeLists.txt b/Native/app/src/main/cpp/CMakeLists.txt index 58fc947..7547575 100644 --- a/Native/app/src/main/cpp/CMakeLists.txt +++ b/Native/app/src/main/cpp/CMakeLists.txt @@ -18,8 +18,6 @@ target_include_directories(native_app_glue PUBLIC add_library(dotnet SHARED IMPORTED) set_property(TARGET dotnet PROPERTY IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../libs/${ANDROID_ABI}/libdotnet.so") -add_library(skia SHARED IMPORTED) -set_property(TARGET skia PROPERTY IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/../libs/${ANDROID_ABI}/libSkiaSharp.so") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") @@ -47,8 +45,5 @@ target_link_libraries(${CMAKE_PROJECT_NAME} # List libraries link to the target library android native_app_glue - skia dotnet - log - EGL - GLESv1_CM) \ No newline at end of file + log) \ No newline at end of file diff --git a/Native/app/src/main/cpp/dotnet.h b/Native/app/src/main/cpp/dotnet.h index 0b3ab2a..8938b91 100644 --- a/Native/app/src/main/cpp/dotnet.h +++ b/Native/app/src/main/cpp/dotnet.h @@ -1,5 +1 @@ -extern "C" void Render(); - -extern "C" void Resize(int width, int height); - -extern "C" void OnTap(float x, float y); +extern "C" int ManagedAdd(int x, int y); diff --git a/Native/app/src/main/cpp/native-lib.cpp b/Native/app/src/main/cpp/native-lib.cpp index a5790c4..ad3fae3 100644 --- a/Native/app/src/main/cpp/native-lib.cpp +++ b/Native/app/src/main/cpp/native-lib.cpp @@ -1,199 +1,36 @@ -#include -#include -#include -#include #include #include #include #include "dotnet.h" -/** - * Shared state for our app. - */ -struct engine { - struct android_app* app; - - int animating = 0; - EGLDisplay display; - EGLSurface surface; - EGLContext context; - int32_t width; - int32_t height; -}; - const char* TAG = "NATIVE"; extern "C" { - void init_display(struct engine* engine) - { - const EGLint configAttribs[] = { - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_BLUE_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_RED_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_STENCIL_SIZE, 8, - EGL_NONE - }; - - const EGLint contextAttribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL_NONE - }; - - const EGLint surfaceAttribs[] = { - EGL_RENDER_BUFFER, EGL_BACK_BUFFER, - EGL_NONE - }; - - EGLint w, h, format; - EGLint numConfigs; - EGLConfig config; - EGLSurface surface; - EGLContext context; - - EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - - eglInitialize(display, NULL, NULL); - - /* Here, the application chooses the configuration it desires. In this - * sample, we have a very simplified selection process, where we pick - * the first EGLConfig that matches our criteria */ - eglChooseConfig(display, configAttribs, &config, 1, &numConfigs); - - /* EGL_NATIVE_VISUAL_ID is an attribute of the EGLConfig that is - * guaranteed to be accepted by ANativeWindow_setBuffersGeometry(). - * As soon as we picked a EGLConfig, we can safely reconfigure the - * ANativeWindow buffers to match, using EGL_NATIVE_VISUAL_ID. */ - eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); - - ANativeWindow_setBuffersGeometry(engine->app->window, 0, 0, format); - - surface = eglCreateWindowSurface(display, config, engine->app->window, surfaceAttribs); - context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); - - if (!eglMakeCurrent(display, surface, surface, context)) { - __android_log_print(ANDROID_LOG_WARN, TAG, "Unable to eglMakeCurrent"); - return; - } - - eglQuerySurface(display, surface, EGL_WIDTH, &w); - eglQuerySurface(display, surface, EGL_HEIGHT, &h); - - __android_log_print(ANDROID_LOG_INFO, TAG, "Canvas size: %d x %d", w, h); - // Call managed - Resize(w, h); - - engine->display = display; - engine->context = context; - engine->surface = surface; - engine->width = w; - engine->height = h; - - // Initialize GL state. - glDisable(GL_DEPTH_TEST); - glEnable(GL_CULL_FACE); - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - glShadeModel(GL_SMOOTH); - - // eglSwapBuffers should not automatically clear the screen - eglSurfaceAttrib(display, surface, EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED); - } - - void term_display(struct engine* engine) { - if (engine->display != EGL_NO_DISPLAY) { - eglMakeCurrent(engine->display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); - if (engine->context != EGL_NO_CONTEXT) { - eglDestroyContext(engine->display, engine->context); - } - if (engine->surface != EGL_NO_SURFACE) { - eglDestroySurface(engine->display, engine->surface); - } - eglTerminate(engine->display); - } - engine->animating = 0; - engine->display = EGL_NO_DISPLAY; - engine->context = EGL_NO_CONTEXT; - engine->surface = EGL_NO_SURFACE; - } - - void draw_frame(struct engine* engine) { - if (engine->display == NULL) return; - - // If we wanted to test OpenGL is working - // glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); - // glClearColor(1, 0, 0, 1); - - // Call into managed - Render(); - - // Swap buffers - eglSwapBuffers(engine->display, engine->surface); - } - void handle_cmd(android_app *pApp, int32_t cmd) { - auto* engine = (struct engine*)pApp->userData; __android_log_print (ANDROID_LOG_INFO, TAG, "handle_cmd: %i", cmd); switch (cmd) { case APP_CMD_INIT_WINDOW: - // The window is being shown, get it ready. - if (engine->app->window != nullptr) { - init_display(engine); - engine->animating = 1; - __android_log_print (ANDROID_LOG_INFO, TAG, "Calling draw_frame() from APP_CMD_INIT_WINDOW"); - draw_frame(engine); - } - break; - case APP_CMD_TERM_WINDOW: - engine->animating = 0; - term_display(engine); - break; - case APP_CMD_GAINED_FOCUS: - engine->animating = 1; - break; - case APP_CMD_LOST_FOCUS: - engine->animating = 0; - __android_log_print (ANDROID_LOG_INFO, TAG, "Calling draw_frame() from APP_CMD_LOST_FOCUS"); - draw_frame(engine); - break; + // TODO: you can handle app lifecycle here default: break; } } int32_t handle_input(struct android_app* app, AInputEvent* event) { - if (AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { - int32_t action = AMotionEvent_getAction(event); - switch (action & AMOTION_EVENT_ACTION_MASK) { - case AMOTION_EVENT_ACTION_DOWN: { - float x = AMotionEvent_getX(event, 0); - float y = AMotionEvent_getY(event, 0); - __android_log_print(ANDROID_LOG_INFO, TAG, - "AMOTION_EVENT_ACTION_DOWN (%f, %f)", x, y); - OnTap(x, y); - break; - } - default: - break; - } - } - + // TODO: you could handle input here return 0; } void android_main(struct android_app *state) { - struct engine engine; - memset(&engine, 0, sizeof(engine)); - state->userData = &engine; state->onAppCmd = handle_cmd; state->onInputEvent = handle_input; - engine.app = state; __android_log_print (ANDROID_LOG_INFO, TAG, "Entering android_main"); + int result = ManagedAdd(1, 2); + __android_log_print (ANDROID_LOG_INFO, TAG, "ManagedAdd(1, 2) returned: %i", result); + int events; android_poll_source *pSource; do { @@ -203,10 +40,7 @@ extern "C" { } } - if (engine.animating) { - __android_log_print (ANDROID_LOG_INFO, TAG, "Calling draw_frame() from loop"); - draw_frame(&engine); - } + // TODO: this is the main message loop } while (!state->destroyRequested); } diff --git a/Native/app/src/main/libs/arm64-v8a/libdotnet.so b/Native/app/src/main/libs/arm64-v8a/libdotnet.so index 1f55fe0..5d4b74f 100644 Binary files a/Native/app/src/main/libs/arm64-v8a/libdotnet.so and b/Native/app/src/main/libs/arm64-v8a/libdotnet.so differ diff --git a/Native/app/src/main/libs/arm64-v8a/libdotnet.so.dbg b/Native/app/src/main/libs/arm64-v8a/libdotnet.so.dbg index dfb82ed..311121e 100644 Binary files a/Native/app/src/main/libs/arm64-v8a/libdotnet.so.dbg and b/Native/app/src/main/libs/arm64-v8a/libdotnet.so.dbg differ