mirror of
https://github.com/Boof2015/astra-mobile.git
synced 2026-08-12 05:10:52 +02:00
fix mali gpu crash, optimize gpu and PCM buffer boundries, reduce gpu memory pressure by 100mb
This commit is contained in:
@@ -16,6 +16,7 @@ android {
|
||||
defaultConfig {
|
||||
versionCode 1
|
||||
versionName "0.1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags "-O3 -std=c++17 -fexceptions -frtti"
|
||||
@@ -24,6 +25,11 @@ android {
|
||||
}
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
pickFirsts += ["**/libc++_shared.so"]
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "CMakeLists.txt"
|
||||
@@ -36,4 +42,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test:runner:1.6.2'
|
||||
}
|
||||
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package expo.modules.astrascope
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ScopeBridgeBoundaryTest {
|
||||
@Test
|
||||
fun rejectsIncompleteOrOverflowingPcmDescriptions() {
|
||||
ScopeBridge.nativeConfigure(48_000, 2)
|
||||
|
||||
// These calls complete without entering the native reader.
|
||||
ScopeBridge.nativePushFrames(FloatArray(3), frameCount = 2, channelCount = 2)
|
||||
ScopeBridge.nativePushFrames(
|
||||
FloatArray(1),
|
||||
frameCount = Int.MAX_VALUE,
|
||||
channelCount = Int.MAX_VALUE
|
||||
)
|
||||
ScopeBridge.nativePushFramesPostEq(FloatArray(3), frameCount = 2, channelCount = 2)
|
||||
ScopeBridge.nativePushFramesPostEq(
|
||||
FloatArray(1),
|
||||
frameCount = Int.MAX_VALUE,
|
||||
channelCount = Int.MAX_VALUE
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun capsNativeWritesToTheDirectBufferCapacity() {
|
||||
ScopeBridge.nativeConfigure(48_000, 1)
|
||||
ScopeBridge.nativePushFrames(FloatArray(12_000), frameCount = 12_000, channelCount = 1)
|
||||
|
||||
val spectrum = directFloats(4)
|
||||
val oscilloscope = directFloats(4)
|
||||
val postEqSpectrum = directFloats(4)
|
||||
|
||||
assertEquals(
|
||||
4,
|
||||
ScopeBridge.nativeFillSpectrum(spectrum, Int.MAX_VALUE, smoothing = 0.92f)
|
||||
)
|
||||
assertEquals(
|
||||
4,
|
||||
ScopeBridge.nativeFillOscilloscope(oscilloscope, Int.MAX_VALUE)
|
||||
)
|
||||
assertEquals(
|
||||
4,
|
||||
ScopeBridge.nativeFillSpectrumPostEq(
|
||||
postEqSpectrum,
|
||||
Int.MAX_VALUE,
|
||||
smoothing = 0.92f
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsNonDirectUndersizedAndMisalignedBuffers() {
|
||||
val heap = ByteBuffer.allocate(16).order(ByteOrder.nativeOrder())
|
||||
val undersized = ByteBuffer.allocateDirect(Float.SIZE_BYTES - 1)
|
||||
.order(ByteOrder.nativeOrder())
|
||||
val misaligned = ByteBuffer.allocateDirect(17).apply { position(1) }
|
||||
.slice()
|
||||
.order(ByteOrder.nativeOrder())
|
||||
|
||||
assertEquals(0, ScopeBridge.nativeFillSpectrum(heap, 4, smoothing = 0.92f))
|
||||
assertEquals(0, ScopeBridge.nativeFillSpectrum(undersized, 4, smoothing = 0.92f))
|
||||
assertEquals(0, ScopeBridge.nativeFillSpectrum(misaligned, 4, smoothing = 0.92f))
|
||||
assertEquals(0, ScopeBridge.nativeFillOscilloscope(heap, 4))
|
||||
assertEquals(
|
||||
0,
|
||||
ScopeBridge.nativeFillSpectrumPostEq(heap, 4, smoothing = 0.92f)
|
||||
)
|
||||
}
|
||||
|
||||
private fun directFloats(count: Int): ByteBuffer =
|
||||
ByteBuffer.allocateDirect(count * Float.SIZE_BYTES).order(ByteOrder.nativeOrder())
|
||||
}
|
||||
+60
-53
@@ -101,14 +101,13 @@ internal class AstraScopeView(
|
||||
private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
private val textureView = TextureView(context)
|
||||
private val renderGate = ScopeRenderGate()
|
||||
private val surfaceSession =
|
||||
ScopeSurfaceSession<SurfaceTexture, Surface> { surface -> surface.release() }
|
||||
private val powerManager =
|
||||
context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
private var attached = false
|
||||
private var windowVisible = false
|
||||
@Volatile
|
||||
private var surfaceAvailable = false
|
||||
@Volatile
|
||||
private var renderSurface: Surface? = null
|
||||
private var scheduledToken = 0
|
||||
private var lastAnalysisAt = 0L
|
||||
private var lastDrawAt = 0L
|
||||
@@ -153,7 +152,7 @@ internal class AstraScopeView(
|
||||
if (!renderGate.isCurrent(token)) return
|
||||
preparePaths()
|
||||
hasNewFrame = false
|
||||
publishFrame()
|
||||
publishFrame(token)
|
||||
}
|
||||
|
||||
if (renderGate.isCurrent(token)) {
|
||||
@@ -185,7 +184,7 @@ internal class AstraScopeView(
|
||||
preparePaths()
|
||||
hasNewFrame = false
|
||||
lastDrawAt = now
|
||||
publishFrame()
|
||||
publishFrame(token)
|
||||
}
|
||||
|
||||
val loopCadence = min(analysisCadence, drawCadence)
|
||||
@@ -201,10 +200,9 @@ internal class AstraScopeView(
|
||||
textureView.isOpaque = false
|
||||
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
|
||||
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
||||
renderSurface?.release()
|
||||
renderSurface = Surface(surface)
|
||||
cancelRendering()
|
||||
surfaceSession.replace(surface, Surface(surface))
|
||||
appliedFrameRate = Float.NaN
|
||||
surfaceAvailable = true
|
||||
restartRendering()
|
||||
}
|
||||
|
||||
@@ -214,10 +212,11 @@ internal class AstraScopeView(
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
||||
surfaceAvailable = false
|
||||
// Invalidate queued/running work before waiting for any publication
|
||||
// already holding the session. close() then releases only this exact
|
||||
// SurfaceTexture's wrapper, after the in-flight canvas has been posted.
|
||||
cancelRendering()
|
||||
renderSurface?.release()
|
||||
renderSurface = null
|
||||
surfaceSession.close(surface)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -250,6 +249,7 @@ internal class AstraScopeView(
|
||||
override fun onDetachedFromWindow() {
|
||||
attached = false
|
||||
cancelRendering()
|
||||
surfaceSession.closeCurrent()
|
||||
super.onDetachedFromWindow()
|
||||
}
|
||||
|
||||
@@ -274,7 +274,8 @@ internal class AstraScopeView(
|
||||
adaptiveFrameDeadline.reset()
|
||||
hasNewFrame = false
|
||||
|
||||
val eligible = attached && windowVisible && surfaceAvailable && width > 0 && height > 0
|
||||
val eligible =
|
||||
attached && windowVisible && surfaceSession.available && width > 0 && height > 0
|
||||
refreshAdaptivePolicy(SystemClock.uptimeMillis(), force = true)
|
||||
scheduledToken = renderGate.update(eligible)
|
||||
applyFrameRateVote(
|
||||
@@ -364,7 +365,7 @@ internal class AstraScopeView(
|
||||
if (staticSnapshot != null && mode == ScopeMode.SPECTRUM) {
|
||||
readScopeFrame()
|
||||
preparePaths()
|
||||
publishFrame()
|
||||
publishFrame(token)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -379,7 +380,7 @@ internal class AstraScopeView(
|
||||
renderedValues.fill(0f, 0, count)
|
||||
}
|
||||
preparePaths()
|
||||
publishFrame()
|
||||
publishFrame(token)
|
||||
|
||||
if (peak >= AstraScopeProjection.REST_EPSILON && renderGate.isCurrent(token)) {
|
||||
val decayFrameMs = if (frameMs > 0.0) frameMs else FALLBACK_FRAME_MS
|
||||
@@ -437,21 +438,22 @@ internal class AstraScopeView(
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
val surface = renderSurface ?: return
|
||||
if (!surface.isValid) return
|
||||
try {
|
||||
if (requestedRate > 0f) {
|
||||
surface.setFrameRate(
|
||||
requestedRate,
|
||||
Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
|
||||
)
|
||||
} else {
|
||||
surface.clearFrameRate()
|
||||
surfaceSession.withCurrent { surface ->
|
||||
if (!surface.isValid) return@withCurrent
|
||||
try {
|
||||
if (requestedRate > 0f) {
|
||||
surface.setFrameRate(
|
||||
requestedRate,
|
||||
Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE
|
||||
)
|
||||
} else {
|
||||
surface.clearFrameRate()
|
||||
}
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// The TextureView may detach while a frame-rate vote is being updated.
|
||||
} catch (_: IllegalStateException) {
|
||||
// The TextureView may detach while a frame-rate vote is being updated.
|
||||
}
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// The TextureView may detach while a frame-rate vote is being updated.
|
||||
} catch (_: IllegalStateException) {
|
||||
// The TextureView may detach while a frame-rate vote is being updated.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,33 +526,38 @@ internal class AstraScopeView(
|
||||
* transparent layer. TextureView publication may schedule a platform frame,
|
||||
* but it never schedules React work or rebuilds the surrounding scene.
|
||||
*/
|
||||
private fun publishFrame() {
|
||||
if (!surfaceAvailable) return
|
||||
val surface = renderSurface ?: return
|
||||
if (!surface.isValid) return
|
||||
val canvas = try {
|
||||
surface.lockHardwareCanvas()
|
||||
} catch (_: IllegalArgumentException) {
|
||||
return
|
||||
} catch (_: IllegalStateException) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
synchronized(pathLock) {
|
||||
if (mode == ScopeMode.SPECTRUM && fillOpacity > 0f) {
|
||||
canvas.drawPath(fillPaths[frontPath], fillPaint)
|
||||
}
|
||||
if (glow) canvas.drawPath(linePaths[frontPath], glowPaint)
|
||||
canvas.drawPath(linePaths[frontPath], strokePaint)
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
surface.unlockCanvasAndPost(canvas)
|
||||
private fun publishFrame(token: Int) {
|
||||
if (!renderGate.isCurrent(token)) return
|
||||
surfaceSession.withCurrentIf(
|
||||
eligible = { renderGate.isCurrent(token) }
|
||||
) { surface ->
|
||||
if (!surface.isValid) return@withCurrentIf
|
||||
val canvas = try {
|
||||
surface.lockHardwareCanvas()
|
||||
} catch (_: Surface.OutOfResourcesException) {
|
||||
return@withCurrentIf
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// The TextureView may detach between lock and post.
|
||||
return@withCurrentIf
|
||||
} catch (_: IllegalStateException) {
|
||||
// The TextureView may detach between lock and post.
|
||||
return@withCurrentIf
|
||||
}
|
||||
try {
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
synchronized(pathLock) {
|
||||
if (mode == ScopeMode.SPECTRUM && fillOpacity > 0f) {
|
||||
canvas.drawPath(fillPaths[frontPath], fillPaint)
|
||||
}
|
||||
if (glow) canvas.drawPath(linePaths[frontPath], glowPaint)
|
||||
canvas.drawPath(linePaths[frontPath], strokePaint)
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
surface.unlockCanvasAndPost(canvas)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// The surface became invalid while the frame was being posted.
|
||||
} catch (_: IllegalStateException) {
|
||||
// The surface became invalid while the frame was being posted.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package expo.modules.astrascope
|
||||
|
||||
/**
|
||||
* Owns one replaceable surface-like resource.
|
||||
*
|
||||
* Publication holds [lock] for the complete use of the resource. Replacement
|
||||
* and teardown therefore wait for an in-flight publication before releasing
|
||||
* it, and an eligibility predicate is rechecked only after the lock is held so
|
||||
* work cancelled while waiting cannot start against a newer resource.
|
||||
*/
|
||||
internal class ScopeSurfaceSession<K : Any, T : Any>(
|
||||
private val release: (T) -> Unit
|
||||
) {
|
||||
private data class Entry<K, T>(
|
||||
val key: K,
|
||||
val resource: T
|
||||
)
|
||||
|
||||
private val lock = Any()
|
||||
private var current: Entry<K, T>? = null
|
||||
|
||||
@Volatile
|
||||
var available: Boolean = false
|
||||
private set
|
||||
|
||||
fun replace(key: K, resource: T) {
|
||||
synchronized(lock) {
|
||||
current?.resource?.let(release)
|
||||
current = Entry(key, resource)
|
||||
available = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Releases only the resource belonging to [key]. A delayed destruction
|
||||
* callback for an older key cannot tear down a replacement.
|
||||
*/
|
||||
fun close(key: K): Boolean =
|
||||
synchronized(lock) {
|
||||
val entry = current ?: return@synchronized false
|
||||
if (entry.key !== key) return@synchronized false
|
||||
|
||||
closeLocked(entry)
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach is also a terminal surface boundary, but Android can deliver it
|
||||
* before the matching TextureView destruction callback. Explicitly closing
|
||||
* the current entry keeps the wrapper out of the runtime cleaner; the later
|
||||
* key-specific callback observes an empty session and does nothing.
|
||||
*/
|
||||
fun closeCurrent(): Boolean =
|
||||
synchronized(lock) {
|
||||
val entry = current ?: return@synchronized false
|
||||
closeLocked(entry)
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs [block] while teardown/replacement is excluded. [eligible] is checked
|
||||
* under the same lock immediately before use.
|
||||
*/
|
||||
fun <R> withCurrentIf(
|
||||
eligible: () -> Boolean,
|
||||
block: (T) -> R
|
||||
): R? =
|
||||
synchronized(lock) {
|
||||
if (!eligible()) return@synchronized null
|
||||
val entry = current ?: return@synchronized null
|
||||
block(entry.resource)
|
||||
}
|
||||
|
||||
/** Runs [block] against the current resource, serialized with teardown. */
|
||||
fun <R> withCurrent(block: (T) -> R): R? =
|
||||
synchronized(lock) {
|
||||
val entry = current ?: return@synchronized null
|
||||
block(entry.resource)
|
||||
}
|
||||
|
||||
private fun closeLocked(entry: Entry<K, T>): Boolean {
|
||||
current = null
|
||||
available = false
|
||||
release(entry.resource)
|
||||
return true
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
package expo.modules.astrascope
|
||||
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import kotlin.concurrent.thread
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class ScopeSurfaceSessionTest {
|
||||
@Test
|
||||
fun teardownWaitsForAnInFlightPublicationBeforeRelease() {
|
||||
val key = Any()
|
||||
val released = Collections.synchronizedList(mutableListOf<String>())
|
||||
val session = ScopeSurfaceSession<Any, String>(released::add)
|
||||
val publicationEntered = CountDownLatch(1)
|
||||
val allowPublicationToFinish = CountDownLatch(1)
|
||||
val closeStarted = CountDownLatch(1)
|
||||
val closeFinished = CountDownLatch(1)
|
||||
session.replace(key, "surface")
|
||||
|
||||
val publisher = thread(name = "scope-publisher") {
|
||||
session.withCurrentIf(eligible = { true }) {
|
||||
publicationEntered.countDown()
|
||||
assertTrue(allowPublicationToFinish.await(2, TimeUnit.SECONDS))
|
||||
}
|
||||
}
|
||||
assertTrue(publicationEntered.await(2, TimeUnit.SECONDS))
|
||||
|
||||
val closer = thread(name = "scope-closer") {
|
||||
closeStarted.countDown()
|
||||
session.close(key)
|
||||
closeFinished.countDown()
|
||||
}
|
||||
assertTrue(closeStarted.await(2, TimeUnit.SECONDS))
|
||||
assertFalse(closeFinished.await(100, TimeUnit.MILLISECONDS))
|
||||
|
||||
allowPublicationToFinish.countDown()
|
||||
assertTrue(closeFinished.await(2, TimeUnit.SECONDS))
|
||||
publisher.join(2_000)
|
||||
closer.join(2_000)
|
||||
|
||||
assertEquals(listOf("surface"), released)
|
||||
assertFalse(session.available)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun cancelledPublicationIsRejectedUnderTheSessionLock() {
|
||||
val eligible = AtomicBoolean(true)
|
||||
val publications = AtomicInteger(0)
|
||||
val session = ScopeSurfaceSession<Any, String> {}
|
||||
session.replace(Any(), "surface")
|
||||
|
||||
assertEquals(
|
||||
"surface",
|
||||
session.withCurrentIf(eligible = eligible::get) {
|
||||
publications.incrementAndGet()
|
||||
it
|
||||
}
|
||||
)
|
||||
|
||||
eligible.set(false)
|
||||
assertNull(
|
||||
session.withCurrentIf(eligible = eligible::get) {
|
||||
publications.incrementAndGet()
|
||||
it
|
||||
}
|
||||
)
|
||||
assertEquals(1, publications.get())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun staleRenderGenerationCannotUseAReplacementSurface() {
|
||||
val firstKey = Any()
|
||||
val secondKey = Any()
|
||||
val session = ScopeSurfaceSession<Any, String> {}
|
||||
val gate = ScopeRenderGate()
|
||||
|
||||
session.replace(firstKey, "first")
|
||||
val firstToken = gate.update(true)
|
||||
gate.update(false)
|
||||
assertTrue(session.close(firstKey))
|
||||
session.replace(secondKey, "second")
|
||||
val secondToken = gate.update(true)
|
||||
|
||||
assertNull(
|
||||
session.withCurrentIf(eligible = { gate.isCurrent(firstToken) }) { it }
|
||||
)
|
||||
assertEquals(
|
||||
"second",
|
||||
session.withCurrentIf(eligible = { gate.isCurrent(secondToken) }) { it }
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun replacementAndDelayedCloseReleaseEachSurfaceExactlyOnce() {
|
||||
val firstKey = Any()
|
||||
val secondKey = Any()
|
||||
val released = mutableListOf<String>()
|
||||
val session = ScopeSurfaceSession<Any, String>(released::add)
|
||||
|
||||
session.replace(firstKey, "first")
|
||||
session.replace(secondKey, "second")
|
||||
assertFalse(session.close(firstKey))
|
||||
assertTrue(session.closeCurrent())
|
||||
assertFalse(session.close(secondKey))
|
||||
assertFalse(session.closeCurrent())
|
||||
|
||||
assertEquals(listOf("first", "second"), released)
|
||||
assertFalse(session.available)
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,48 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
#include "scope_ring.h"
|
||||
|
||||
namespace {
|
||||
astra::ScopeDriver& driver() { return astra::ScopeDriver::instance(); }
|
||||
|
||||
bool hasCompleteInterleavedFrames(
|
||||
JNIEnv* env, jfloatArray frames, jint frameCount, jint channelCount) {
|
||||
if (frames == nullptr || frameCount <= 0 || channelCount <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Widen before multiplying so hostile or corrupted JNI arguments cannot
|
||||
// overflow and turn an undersized Java array into an out-of-bounds native read.
|
||||
const int64_t required =
|
||||
static_cast<int64_t>(frameCount) * static_cast<int64_t>(channelCount);
|
||||
return required <= static_cast<int64_t>(env->GetArrayLength(frames));
|
||||
}
|
||||
|
||||
size_t directFloatCapacity(
|
||||
JNIEnv* env, jobject buffer, jint requestedFloats, float** address) {
|
||||
*address = nullptr;
|
||||
if (buffer == nullptr || requestedFloats <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* raw = env->GetDirectBufferAddress(buffer);
|
||||
const jlong capacityBytes = env->GetDirectBufferCapacity(buffer);
|
||||
if (raw == nullptr || capacityBytes < static_cast<jlong>(sizeof(float))) {
|
||||
return 0;
|
||||
}
|
||||
if (reinterpret_cast<uintptr_t>(raw) % alignof(float) != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*address = static_cast<float*>(raw);
|
||||
const size_t actualFloats =
|
||||
static_cast<size_t>(capacityBytes / static_cast<jlong>(sizeof(float)));
|
||||
return std::min(static_cast<size_t>(requestedFloats), actualFloats);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
@@ -26,7 +64,7 @@ JNIEXPORT void JNICALL
|
||||
Java_expo_modules_astrascope_ScopeBridge_nativePushFrames(
|
||||
JNIEnv* env, jobject /*thiz*/, jfloatArray frames, jint frameCount,
|
||||
jint channelCount) {
|
||||
if (frames == nullptr || frameCount <= 0 || channelCount <= 0) {
|
||||
if (!hasCompleteInterleavedFrames(env, frames, frameCount, channelCount)) {
|
||||
return;
|
||||
}
|
||||
auto* data = static_cast<float*>(
|
||||
@@ -47,15 +85,13 @@ JNIEXPORT jint JNICALL
|
||||
Java_expo_modules_astrascope_ScopeBridge_nativeFillSpectrum(
|
||||
JNIEnv* env, jobject /*thiz*/, jobject buffer, jint capacityFloats,
|
||||
jfloat smoothing) {
|
||||
if (buffer == nullptr || capacityFloats <= 0) {
|
||||
return 0;
|
||||
}
|
||||
auto* dst = static_cast<float*>(env->GetDirectBufferAddress(buffer));
|
||||
if (dst == nullptr) {
|
||||
float* dst = nullptr;
|
||||
const size_t capacity = directFloatCapacity(env, buffer, capacityFloats, &dst);
|
||||
if (capacity == 0) {
|
||||
return 0;
|
||||
}
|
||||
const size_t n = driver().fillSpectrum(
|
||||
dst, static_cast<size_t>(capacityFloats), static_cast<float>(smoothing));
|
||||
dst, capacity, static_cast<float>(smoothing));
|
||||
return static_cast<jint>(n);
|
||||
}
|
||||
|
||||
@@ -64,14 +100,12 @@ Java_expo_modules_astrascope_ScopeBridge_nativeFillSpectrum(
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_expo_modules_astrascope_ScopeBridge_nativeFillOscilloscope(
|
||||
JNIEnv* env, jobject /*thiz*/, jobject buffer, jint capacityFloats) {
|
||||
if (buffer == nullptr || capacityFloats <= 0) {
|
||||
float* dst = nullptr;
|
||||
const size_t capacity = directFloatCapacity(env, buffer, capacityFloats, &dst);
|
||||
if (capacity == 0) {
|
||||
return 0;
|
||||
}
|
||||
auto* dst = static_cast<float*>(env->GetDirectBufferAddress(buffer));
|
||||
if (dst == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
const size_t n = driver().fillOscilloscope(dst, static_cast<size_t>(capacityFloats));
|
||||
const size_t n = driver().fillOscilloscope(dst, capacity);
|
||||
return static_cast<jint>(n);
|
||||
}
|
||||
|
||||
@@ -82,7 +116,7 @@ JNIEXPORT void JNICALL
|
||||
Java_expo_modules_astrascope_ScopeBridge_nativePushFramesPostEq(
|
||||
JNIEnv* env, jobject /*thiz*/, jfloatArray frames, jint frameCount,
|
||||
jint channelCount) {
|
||||
if (frames == nullptr || frameCount <= 0 || channelCount <= 0) {
|
||||
if (!hasCompleteInterleavedFrames(env, frames, frameCount, channelCount)) {
|
||||
return;
|
||||
}
|
||||
auto* data = static_cast<float*>(
|
||||
@@ -99,15 +133,13 @@ JNIEXPORT jint JNICALL
|
||||
Java_expo_modules_astrascope_ScopeBridge_nativeFillSpectrumPostEq(
|
||||
JNIEnv* env, jobject /*thiz*/, jobject buffer, jint capacityFloats,
|
||||
jfloat smoothing) {
|
||||
if (buffer == nullptr || capacityFloats <= 0) {
|
||||
return 0;
|
||||
}
|
||||
auto* dst = static_cast<float*>(env->GetDirectBufferAddress(buffer));
|
||||
if (dst == nullptr) {
|
||||
float* dst = nullptr;
|
||||
const size_t capacity = directFloatCapacity(env, buffer, capacityFloats, &dst);
|
||||
if (capacity == 0) {
|
||||
return 0;
|
||||
}
|
||||
const size_t n = driver().fillSpectrumPostEq(
|
||||
dst, static_cast<size_t>(capacityFloats), static_cast<float>(smoothing));
|
||||
dst, capacity, static_cast<float>(smoothing));
|
||||
return static_cast<jint>(n);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,15 +60,21 @@ index e8e4c03..0ce542c 100644
|
||||
jclass surfaceClass = env->FindClass("android/view/Surface");
|
||||
jmethodID surfaceConstructor = env->GetMethodID(
|
||||
surfaceClass, "<init>", "(Landroid/graphics/SurfaceTexture;)V");
|
||||
@@ -95,15 +84,10 @@ void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject jSurfaceTexture,
|
||||
@@ -95,15 +84,15 @@ void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject jSurfaceTexture,
|
||||
env->NewObject(surfaceClass, surfaceConstructor, jSurfaceTexture);
|
||||
+ jmethodID surfaceRelease =
|
||||
+ env->GetMethodID(surfaceClass, "release", "()V");
|
||||
window = ANativeWindow_fromSurface(env, jSurface);
|
||||
|
||||
- jclass surfaceTextureClass = env->GetObjectClass(_jSurfaceTexture);
|
||||
- _updateTexImageMethod =
|
||||
- env->GetMethodID(surfaceTextureClass, "updateTexImage", "()V");
|
||||
-
|
||||
// Acquire the native window from the Surface
|
||||
- // Acquire the native window from the Surface
|
||||
+ // ANativeWindow_fromSurface() acquired an independent native reference, so
|
||||
+ // close the temporary Java wrapper before dropping its local reference.
|
||||
+ env->CallVoidMethod(jSurface, surfaceRelease);
|
||||
+
|
||||
// Clean up local references
|
||||
env->DeleteLocalRef(jSurface);
|
||||
env->DeleteLocalRef(surfaceClass);
|
||||
@@ -76,7 +82,7 @@ index e8e4c03..0ce542c 100644
|
||||
} else {
|
||||
window = ANativeWindow_fromSurface(env, jSurfaceTexture);
|
||||
}
|
||||
@@ -112,6 +96,12 @@ void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject jSurfaceTexture,
|
||||
@@ -112,6 +101,12 @@ void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject jSurfaceTexture,
|
||||
#else
|
||||
_surfaceHolder = OpenGLContext::getInstance().MakeWindow(window);
|
||||
#endif
|
||||
@@ -89,7 +95,7 @@ index e8e4c03..0ce542c 100644
|
||||
|
||||
// Post redraw request to ensure we paint in the next draw cycle.
|
||||
_requestRedraw();
|
||||
@@ -120,11 +110,15 @@ void RNSkOpenGLCanvasProvider::surfaceDestroyed() {
|
||||
@@ -120,11 +115,15 @@ void RNSkOpenGLCanvasProvider::surfaceDestroyed() {
|
||||
// destroy the renderer (a unique pointer so the dtor will be called
|
||||
// immediately.)
|
||||
_surfaceHolder = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user