Skip to content

Commit b00c824

Browse files
authored
High density framebuffer and supersampling support (#2801)
* high dpi and supersampling * always use blit framebuffer
1 parent 438fb94 commit b00c824

17 files changed

Lines changed: 1111 additions & 374 deletions

File tree

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ checkstyle = "13.3.0"
66
jacoco = "0.8.12"
77
lwjgl3 = "3.4.1"
88
angle = "2026-05-09"
9-
libjglios = "0.2"
9+
libjglios = "0.3"
1010
saferalloc = "0.0.8"
1111
nifty = "1.4.3"
1212
spotbugs = "4.9.8"

jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,11 @@ public void reshape(int width, int height) {
225225
app.reshape(width, height);
226226
}
227227

228+
@Override
229+
public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) {
230+
app.reshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight);
231+
}
232+
228233
@Override
229234
public void rescale(float x, float y) {
230235
app.rescale(x, y);

jme3-android/src/main/java/com/jme3/input/android/AndroidMouseInput14.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,9 @@ public void loadSettings(AppSettings settings) {
145145
if (inputHandler.getView().getWidth() != 0 && inputHandler.getView().getHeight() != 0) {
146146
scaleX = settings.getWidth() / (float)inputHandler.getView().getWidth();
147147
scaleY = settings.getHeight() / (float)inputHandler.getView().getHeight();
148-
currentMouseState.setStartPosition(inputHandler.getView().getWidth()/2, inputHandler.getView().getHeight()/2);
148+
currentMouseState.setStartPosition(
149+
getJmeX(inputHandler.getView().getWidth() / 2f),
150+
getJmeY(inputHandler.getView().getHeight() / 2f));
149151
}
150152

151153
if (logger.isLoggable(Level.FINE)) {

jme3-android/src/main/java/com/jme3/system/android/OGLESContext.java

Lines changed: 112 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,10 @@
3737
import android.content.DialogInterface;
3838
import android.content.pm.ConfigurationInfo;
3939
import android.graphics.PixelFormat;
40-
import android.graphics.Rect;
4140
import android.opengl.GLSurfaceView;
4241
import android.os.Build;
4342
import android.text.InputType;
4443
import android.view.Gravity;
45-
import android.view.SurfaceHolder;
46-
import android.view.SurfaceView;
4744
import android.view.View;
4845
import android.view.ViewGroup.LayoutParams;
4946
import android.widget.EditText;
@@ -58,6 +55,7 @@
5855
import com.jme3.input.controls.SoftTextDialogInputListener;
5956
import com.jme3.input.dummy.DummyKeyInput;
6057
import com.jme3.material.Material;
58+
import com.jme3.math.Vector2f;
6159
import com.jme3.renderer.Caps;
6260
import com.jme3.renderer.Camera;
6361
import com.jme3.renderer.RenderManager;
@@ -68,6 +66,7 @@
6866
import com.jme3.texture.FrameBuffer.FrameBufferTarget;
6967
import com.jme3.texture.Image;
7068
import com.jme3.texture.Image.Format;
69+
import com.jme3.texture.Texture;
7170
import com.jme3.texture.Texture2D;
7271
import com.jme3.texture.image.ColorSpace;
7372
import com.jme3.ui.Picture;
@@ -99,6 +98,12 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex
9998
protected AndroidInputHandler androidInput;
10099
protected long minFrameDuration = 0; // No FPS cap
101100
protected long lastUpdateTime = 0;
101+
private int logicalWidth = 1;
102+
private int logicalHeight = 1;
103+
private int framebufferWidth = 1;
104+
private int framebufferHeight = 1;
105+
private final Vector2f displayScale = new Vector2f(1f, 1f);
106+
private float appliedDisplayScaleMode = Float.NaN;
102107
private Application application;
103108
private Material blitMaterial;
104109
private Picture blitGeometry;
@@ -452,16 +457,62 @@ public void onSurfaceChanged(GL10 gl, int width, int height) {
452457
new Object[] { width, height }
453458
);
454459
}
455-
// update the application settings with the new resolution
456-
settings.setResolution(width, height);
457-
// Reload settings in androidInput so the correct touch event scaling can be
458-
// calculated in case the surface resolution is different than the view.
459-
androidInput.loadSettings(settings);
460+
framebufferWidth = Math.max(width, 1);
461+
framebufferHeight = Math.max(height, 1);
462+
updateDisplayScaleMetrics();
460463
// if the application has already been initialized (ie renderable is set)
461464
// then call reshape so the app can adjust to the new resolution.
462465
if (renderable.get()) {
463466
logger.log(Level.FINE, "App already initialized, calling reshape");
464-
listener.reshape(width, height);
467+
listener.reshape(logicalWidth, logicalHeight, getRenderFramebufferWidth(), getRenderFramebufferHeight());
468+
listener.rescale(displayScale.x, displayScale.y);
469+
}
470+
}
471+
472+
private float getAndroidDisplayDensity() {
473+
if (androidInput != null && androidInput.getView() != null
474+
&& androidInput.getView().getResources() != null) {
475+
android.util.DisplayMetrics metrics = androidInput.getView().getResources().getDisplayMetrics();
476+
if (metrics != null) {
477+
if (metrics.density > 0f) {
478+
return metrics.density;
479+
}
480+
if (metrics.densityDpi > 0) {
481+
return metrics.densityDpi / 160f;
482+
}
483+
}
484+
}
485+
return 1f;
486+
}
487+
488+
private void updateDisplayScaleMetrics() {
489+
float density = DisplayScaleUtils.sanitizeScale(getAndroidDisplayDensity());
490+
displayScale.set(density, density);
491+
appliedDisplayScaleMode = settings.getDisplayScaleMode();
492+
if (DisplayScaleUtils.isNativePixelsMode(appliedDisplayScaleMode)) {
493+
logicalWidth = framebufferWidth;
494+
logicalHeight = framebufferHeight;
495+
} else {
496+
logicalWidth = Math.max(Math.round(framebufferWidth / density), 1);
497+
logicalHeight = Math.max(Math.round(framebufferHeight / density), 1);
498+
}
499+
settings.setResolution(logicalWidth, logicalHeight);
500+
// Reload settings in androidInput so the correct touch event scaling can be
501+
// calculated in case the surface resolution is different than the view.
502+
if (androidInput != null) {
503+
androidInput.loadSettings(settings);
504+
}
505+
}
506+
507+
private void applyDisplayScaleModeIfNeeded() {
508+
if (Float.compare(settings.getDisplayScaleMode(), appliedDisplayScaleMode) == 0) {
509+
return;
510+
}
511+
512+
updateDisplayScaleMetrics();
513+
if (renderable.get()) {
514+
listener.reshape(logicalWidth, logicalHeight, getRenderFramebufferWidth(), getRenderFramebufferHeight());
515+
listener.rescale(displayScale.x, displayScale.y);
465516
}
466517
}
467518

@@ -475,15 +526,21 @@ public void onDrawFrame(GL10 gl) {
475526

476527
if (!renderable.get()) {
477528
if (created.get()) {
529+
applyDisplayScaleModeIfNeeded();
478530
logger.fine("GL Surface is setup, initializing application");
479531
listener.initialize();
532+
if (framebufferWidth > 0 && framebufferHeight > 0) {
533+
listener.reshape(logicalWidth, logicalHeight, getRenderFramebufferWidth(), getRenderFramebufferHeight());
534+
listener.rescale(displayScale.x, displayScale.y);
535+
}
480536
renderable.set(true);
481537
}
482538
} else {
483539
if (!created.get()) {
484540
throw new IllegalStateException("onDrawFrame without create");
485541
}
486542

543+
applyDisplayScaleModeIfNeeded();
487544
if (!renderFrameWithBlitSrgbConversion()) {
488545
listener.update();
489546
}
@@ -549,12 +606,43 @@ private boolean useBlitSrgbConversion() {
549606
return settings.isGammaCorrection() && application != null;
550607
}
551608

609+
private boolean useBlitFrameBuffer() {
610+
float mode = settings.getDisplayScaleMode();
611+
return application != null && (useBlitSrgbConversion()
612+
|| DisplayScaleUtils.isDisabledMode(mode) || DisplayScaleUtils.isEmulatedScaleMode(mode));
613+
}
614+
615+
private int getRenderFramebufferWidth() {
616+
float mode = settings.getDisplayScaleMode();
617+
if (DisplayScaleUtils.isDisabledMode(mode)) {
618+
return Math.max(logicalWidth, 1);
619+
}
620+
if (DisplayScaleUtils.isEmulatedScaleMode(mode)) {
621+
return Math.max(Math.round(framebufferWidth * mode), 1);
622+
}
623+
return Math.max(framebufferWidth, 1);
624+
}
625+
626+
private int getRenderFramebufferHeight() {
627+
float mode = settings.getDisplayScaleMode();
628+
if (DisplayScaleUtils.isDisabledMode(mode)) {
629+
return Math.max(logicalHeight, 1);
630+
}
631+
if (DisplayScaleUtils.isEmulatedScaleMode(mode)) {
632+
return Math.max(Math.round(framebufferHeight * mode), 1);
633+
}
634+
return Math.max(framebufferHeight, 1);
635+
}
636+
552637
private int getLinearFrameBufferSampleCount() {
553638
int samples = Math.max(settings.getSamples(), 1);
554-
if (samples > 1 && renderer != null && !renderer.getCaps().contains(Caps.TextureMultisample)) {
639+
if (samples > 1 && renderer != null
640+
&& (!renderer.getCaps().contains(Caps.TextureMultisample)
641+
|| !renderer.getCaps().contains(Caps.OpenGL32))) {
555642
if (!multisampleTextureWarningIssued) {
556-
logger.warning("sRGB blit conversion requires multisampled textures for MSAA. "
557-
+ "Falling back to a single-sample linear framebuffer.");
643+
logger.log(Level.WARNING,
644+
"Display scale blit requested {0}x MSAA, but this backend cannot sample multisample textures for the blit path. Falling back to a single-sample linear framebuffer.",
645+
samples);
558646
multisampleTextureWarningIssued = true;
559647
}
560648
return 1;
@@ -563,13 +651,13 @@ private int getLinearFrameBufferSampleCount() {
563651
}
564652

565653
private void rebuildLinearFrameBufferIfNeeded() {
566-
if (!useBlitSrgbConversion()) {
654+
if (!useBlitFrameBuffer()) {
567655
destroyLinearFrameBufferResources();
568656
return;
569657
}
570658

571-
int width = Math.max(settings.getWidth(), 1);
572-
int height = Math.max(settings.getHeight(), 1);
659+
int width = getRenderFramebufferWidth();
660+
int height = getRenderFramebufferHeight();
573661
int samples = getLinearFrameBufferSampleCount();
574662

575663
if (linearFrameBuffer != null && linearFrameBuffer.getWidth() == width
@@ -585,6 +673,8 @@ private void rebuildLinearFrameBufferIfNeeded() {
585673

586674
Texture2D colorTexture = new Texture2D(
587675
new Image(getLinearFrameBufferColorFormat(), width, height, null, ColorSpace.Linear));
676+
colorTexture.setMagFilter(Texture.MagFilter.Bilinear);
677+
colorTexture.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
588678
if (samples > 1) {
589679
colorTexture.getImage().setMultiSamples(samples);
590680
}
@@ -610,7 +700,7 @@ private Format getLinearFrameBufferColorFormat() {
610700
}
611701

612702
private boolean ensureBlitResources() {
613-
if (!useBlitSrgbConversion()) {
703+
if (!useBlitFrameBuffer()) {
614704
return false;
615705
}
616706

@@ -622,10 +712,10 @@ private boolean ensureBlitResources() {
622712

623713
if (blitMaterial == null) {
624714
blitMaterial = new Material(assetManager, BLIT_MATERIAL);
625-
blitMaterial.setBoolean("Srgb", true);
626715
blitMaterial.getAdditionalRenderState().setDepthTest(false);
627716
blitMaterial.getAdditionalRenderState().setDepthWrite(false);
628717
}
718+
blitMaterial.setBoolean("Srgb", useBlitSrgbConversion());
629719

630720
if (blitGeometry == null) {
631721
blitGeometry = new Picture("Linear to sRGB Blit");
@@ -667,7 +757,7 @@ private void destroyLinearFrameBufferResources() {
667757
}
668758

669759
private boolean renderFrameWithBlitSrgbConversion() {
670-
if (!useBlitSrgbConversion()) {
760+
if (!useBlitFrameBuffer()) {
671761
return false;
672762
}
673763

@@ -699,8 +789,8 @@ private boolean renderFrameWithBlitSrgbConversion() {
699789
Camera previousCamera = renderManager.getCurrentCamera();
700790
try {
701791
renderer.setFrameBuffer(null);
702-
int blitWidth = linearFrameBuffer.getWidth();
703-
int blitHeight = linearFrameBuffer.getHeight();
792+
int blitWidth = Math.max(getFramebufferWidth(), 1);
793+
int blitHeight = Math.max(getFramebufferHeight(), 1);
704794
if (blitCamera.getWidth() != blitWidth || blitCamera.getHeight() != blitHeight) {
705795
blitCamera.resize(blitWidth, blitHeight, true);
706796
}
@@ -811,16 +901,9 @@ public void onClick(DialogInterface dialog, int whichButton) {
811901
);
812902
}
813903

814-
/**
815-
* Returns the height of the input surface.
816-
*
817-
* @return the height (in pixels)
818-
*/
819904
@Override
820905
public int getFramebufferHeight() {
821-
Rect rect = getSurfaceFrame();
822-
int result = rect.height();
823-
return result;
906+
return framebufferHeight;
824907
}
825908

826909
/**
@@ -830,9 +913,7 @@ public int getFramebufferHeight() {
830913
*/
831914
@Override
832915
public int getFramebufferWidth() {
833-
Rect rect = getSurfaceFrame();
834-
int result = rect.width();
835-
return result;
916+
return framebufferWidth;
836917
}
837918

838919
/**
@@ -855,19 +936,6 @@ public int getWindowYPosition() {
855936
throw new UnsupportedOperationException("not implemented yet");
856937
}
857938

858-
/**
859-
* Retrieves the dimensions of the input surface. Note: do not modify the
860-
* returned object.
861-
*
862-
* @return the dimensions (in pixels, left and top are 0)
863-
*/
864-
private Rect getSurfaceFrame() {
865-
SurfaceView view = (SurfaceView) androidInput.getView();
866-
SurfaceHolder holder = view.getHolder();
867-
Rect result = holder.getSurfaceFrame();
868-
return result;
869-
}
870-
871939
@Override
872940
public Displays getDisplays() {
873941
// TODO Auto-generated method stub

jme3-core/src/main/java/com/jme3/app/LegacyApplication.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -593,8 +593,13 @@ public void startCanvas(boolean waitFor) {
593593
*/
594594
@Override
595595
public void reshape(int w, int h) {
596+
reshape(w, h, w, h);
597+
}
598+
599+
@Override
600+
public void reshape(int logicalWidth, int logicalHeight, int framebufferWidth, int framebufferHeight) {
596601
if (renderManager != null) {
597-
renderManager.notifyReshape(w, h);
602+
renderManager.notifyReshape(logicalWidth, logicalHeight, framebufferWidth, framebufferHeight);
598603
}
599604
}
600605

0 commit comments

Comments
 (0)