|
15 | 15 | import com.badlogic.gdx.graphics.Texture; |
16 | 16 | import com.badlogic.gdx.graphics.g2d.BitmapFont; |
17 | 17 | import com.badlogic.gdx.graphics.g2d.SpriteBatch; |
| 18 | +import com.badlogic.gdx.graphics.glutils.ShaderProgram; |
18 | 19 | import com.badlogic.gdx.scenes.scene2d.InputEvent; |
19 | 20 | import com.badlogic.gdx.scenes.scene2d.Stage; |
20 | 21 | import com.badlogic.gdx.scenes.scene2d.ui.Image; |
|
39 | 40 | */ |
40 | 41 | public class MachineScreen implements Screen { |
41 | 42 |
|
| 43 | + private enum ScreenFilterMode { |
| 44 | + NEAREST, |
| 45 | + SOFT, |
| 46 | + LINEAR |
| 47 | + } |
| 48 | + |
| 49 | + private static final float SOFT_FILTER_BLEND = 0.4f; |
| 50 | + |
| 51 | + private static final String SOFT_FILTER_VERTEX_SHADER = "attribute vec4 a_position;\n" |
| 52 | + + "attribute vec4 a_color;\n" |
| 53 | + + "attribute vec2 a_texCoord0;\n" |
| 54 | + + "uniform mat4 u_projTrans;\n" |
| 55 | + + "varying vec4 v_color;\n" |
| 56 | + + "varying vec2 v_texCoords;\n" |
| 57 | + + "\n" |
| 58 | + + "void main() {\n" |
| 59 | + + " v_color = a_color;\n" |
| 60 | + + " v_texCoords = a_texCoord0;\n" |
| 61 | + + " gl_Position = u_projTrans * a_position;\n" |
| 62 | + + "}\n"; |
| 63 | + |
| 64 | + private static final String SOFT_FILTER_FRAGMENT_SHADER = "#ifdef GL_ES\n" |
| 65 | + + "precision mediump float;\n" |
| 66 | + + "#endif\n" |
| 67 | + + "\n" |
| 68 | + + "varying vec4 v_color;\n" |
| 69 | + + "varying vec2 v_texCoords;\n" |
| 70 | + + "uniform sampler2D u_texture;\n" |
| 71 | + + "uniform sampler2D u_textureLinear;\n" |
| 72 | + + "uniform vec2 u_textureSize;\n" |
| 73 | + + "uniform float u_softness;\n" |
| 74 | + + "\n" |
| 75 | + + "void main() {\n" |
| 76 | + + " vec2 texelSize = vec2(1.0) / u_textureSize;\n" |
| 77 | + + " vec2 nearestCoords = (floor(v_texCoords * u_textureSize) + 0.5) * texelSize;\n" |
| 78 | + + " nearestCoords = clamp(nearestCoords, texelSize * 0.5, vec2(1.0) - (texelSize * 0.5));\n" |
| 79 | + + " vec4 nearestColor = texture2D(u_texture, nearestCoords);\n" |
| 80 | + + " vec4 linearColor = texture2D(u_textureLinear, v_texCoords);\n" |
| 81 | + + " gl_FragColor = v_color * mix(nearestColor, linearColor, u_softness);\n" |
| 82 | + + "}\n"; |
| 83 | + |
42 | 84 | /** |
43 | 85 | * The Game object for JVic. Allows us to easily change screens. |
44 | 86 | */ |
@@ -72,9 +114,13 @@ public class MachineScreen implements Screen { |
72 | 114 | private ExtendViewport viewport; |
73 | 115 | private Camera camera; |
74 | 116 | private Texture[] screens; |
| 117 | + |
75 | 118 | private int drawScreen = 1; |
76 | 119 | private int updateScreen = 0; |
77 | | - private int textureOffset = 0; |
| 120 | + |
| 121 | + private ScreenFilterMode screenFilterMode = ScreenFilterMode.NEAREST; |
| 122 | + private float softFilterBlend = SOFT_FILTER_BLEND; |
| 123 | + private ShaderProgram softFilterShader; |
78 | 124 |
|
79 | 125 | // Screen resources for each MachineType. |
80 | 126 | private Map<MachineType, Pixmap> machineTypePixmaps; |
@@ -149,6 +195,7 @@ public MachineScreen(JVic jvic, JVicRunner jvicRunner, DialogHandler dialogHandl |
149 | 195 | jvicRunner.init(this, machineType.getTotalScreenWidth(), machineType.getTotalScreenHeight()); |
150 | 196 |
|
151 | 197 | batch = new SpriteBatch(); |
| 198 | + softFilterShader = createSoftFilterShader(); |
152 | 199 |
|
153 | 200 | machineTypePixmaps = new HashMap<MachineType, Pixmap>(); |
154 | 201 | machineTypeTextures = new HashMap<MachineType, Texture[]>(); |
@@ -205,7 +252,7 @@ public MachineScreen(JVic jvic, JVicRunner jvicRunner, DialogHandler dialogHandl |
205 | 252 |
|
206 | 253 | // If the application is running on mobile web, then show FPS enabled, but |
207 | 254 | // for desktop web, turn off by default. |
208 | | - showFPS = jvicRunner.isMobile(); |
| 255 | + //showFPS = jvicRunner.isMobile(); |
209 | 256 |
|
210 | 257 | // FPS font |
211 | 258 | font = new BitmapFont(Gdx.files.internal("data/default.fnt"), false); |
@@ -264,12 +311,8 @@ public void initMachine(AppConfigItem appConfigItem, boolean startedByUser) { |
264 | 311 | drawScreen = 1; |
265 | 312 | updateScreen = 0; |
266 | 313 |
|
267 | | - // Blur or sharp pixels. |
268 | | - if ("nearest".equalsIgnoreCase(appConfigItem.getTextureFilter())) { |
269 | | - textureOffset = 0; |
270 | | - } else { |
271 | | - textureOffset = 3; |
272 | | - } |
| 314 | + setTextureFilterMode(appConfigItem.getTextureFilter()); |
| 315 | + clearActiveScreenBuffers(); |
273 | 316 | } |
274 | 317 |
|
275 | 318 | private void activateScreenResources(MachineType machineType) { |
@@ -386,11 +429,24 @@ public void render(float delta) { |
386 | 429 | public boolean copyPixels() { |
387 | 430 | ensureActiveScreenResources(); |
388 | 431 | jvicRunner.updatePixmap(screenPixmap); |
389 | | - screens[updateScreen + textureOffset].draw(screenPixmap, 0, 0); |
| 432 | + ScreenFilterMode activeFilterMode = getActiveScreenFilterMode(); |
| 433 | + screens[updateScreen + getTextureOffset(activeFilterMode)].draw(screenPixmap, 0, 0); |
| 434 | + if (activeFilterMode == ScreenFilterMode.SOFT) { |
| 435 | + screens[updateScreen + getTextureOffset(ScreenFilterMode.LINEAR)].draw(screenPixmap, 0, 0); |
| 436 | + } |
390 | 437 | updateScreen = (updateScreen + 1) % 3; |
391 | 438 | drawScreen = (drawScreen + 1) % 3; |
392 | 439 | return true; |
393 | 440 | } |
| 441 | + |
| 442 | + private void clearActiveScreenBuffers() { |
| 443 | + ensureActiveScreenResources(); |
| 444 | + screenPixmap.setColor(0, 0, 0, 1); |
| 445 | + screenPixmap.fill(); |
| 446 | + for (Texture screen : screens) { |
| 447 | + screen.draw(screenPixmap, 0, 0); |
| 448 | + } |
| 449 | + } |
394 | 450 |
|
395 | 451 | private void draw(float delta) { |
396 | 452 | // Get the KeyboardType currently being used by the MachineScreenProcessor. |
@@ -452,18 +508,33 @@ private void draw(float delta) { |
452 | 508 | camera.update(); |
453 | 509 | batch.setProjectionMatrix(camera.combined); |
454 | 510 | batch.disableBlending(); |
| 511 | + ScreenFilterMode activeFilterMode = getActiveScreenFilterMode(); |
| 512 | + if (activeFilterMode == ScreenFilterMode.SOFT) { |
| 513 | + batch.setShader(softFilterShader); |
| 514 | + } |
455 | 515 | batch.begin(); |
456 | 516 | Color c = batch.getColor(); |
457 | 517 | batch.setColor(c.r, c.g, c.b, 1f); |
| 518 | + if (activeFilterMode == ScreenFilterMode.SOFT) { |
| 519 | + Texture linearScreen = screens[drawScreen + getTextureOffset(ScreenFilterMode.LINEAR)]; |
| 520 | + linearScreen.bind(1); |
| 521 | + softFilterShader.setUniformi("u_textureLinear", 1); |
| 522 | + softFilterShader.setUniformf("u_textureSize", (float) linearScreen.getWidth(), (float) linearScreen.getHeight()); |
| 523 | + softFilterShader.setUniformf("u_softness", softFilterBlend); |
| 524 | + Gdx.gl.glActiveTexture(GL20.GL_TEXTURE0); |
| 525 | + } |
458 | 526 |
|
459 | 527 | // Texture isn't always drawn to match physical pixels. |
460 | 528 | batch.draw( |
461 | | - screens[drawScreen + textureOffset], |
| 529 | + screens[drawScreen + getTextureOffset(activeFilterMode)], |
462 | 530 | 0, 0, renderWidth, renderHeight, |
463 | 531 | machineType.getHorizontalOffset(), machineType.getVerticalOffset(), |
464 | 532 | machineType.getVisibleScreenWidth(), machineType.getVisibleScreenHeight(), |
465 | 533 | false, false); |
466 | 534 | batch.end(); |
| 535 | + if (activeFilterMode == ScreenFilterMode.SOFT) { |
| 536 | + batch.setShader(null); |
| 537 | + } |
467 | 538 |
|
468 | 539 | // Render the UI elements, e.g. the keyboard and joystick icons. |
469 | 540 | viewportManager.getCurrentCamera().update(); |
@@ -542,9 +613,7 @@ private void draw(float delta) { |
542 | 613 |
|
543 | 614 | if (showFPS) { |
544 | 615 | StringBuilder overlayText = new StringBuilder(); |
545 | | - overlayText.append("FPS: "); |
546 | | - overlayText.append(Gdx.graphics.getFramesPerSecond()); |
547 | | - |
| 616 | + |
548 | 617 | String performanceStats = jvicRunner.getPerformanceStatsText(); |
549 | 618 | if ((performanceStats != null) && !performanceStats.isEmpty()) { |
550 | 619 | overlayText.append('\n'); |
@@ -875,6 +944,9 @@ public void dispose() { |
875 | 944 | screenSizeIcon.dispose(); |
876 | 945 | warpSpeedIcon.dispose(); |
877 | 946 | cameraIcon.dispose(); |
| 947 | + if (softFilterShader != null) { |
| 948 | + softFilterShader.dispose(); |
| 949 | + } |
878 | 950 | batch.dispose(); |
879 | 951 | jvicRunner.stop(); |
880 | 952 | disposeScreens(); |
@@ -903,7 +975,65 @@ private void disposeScreens() { |
903 | 975 | * @param blurOn |
904 | 976 | */ |
905 | 977 | public void changeBlur(boolean blurOn) { |
906 | | - textureOffset = (blurOn? 3 : 0); |
| 978 | + setTextureFilterMode(blurOn ? "linear" : "nearest"); |
| 979 | + } |
| 980 | + |
| 981 | + public void changeTextureFilter(String textureFilter) { |
| 982 | + setTextureFilterMode(textureFilter); |
| 983 | + } |
| 984 | + |
| 985 | + private void setTextureFilterMode(String textureFilter) { |
| 986 | + if (textureFilter == null) { |
| 987 | + screenFilterMode = ScreenFilterMode.SOFT; |
| 988 | + softFilterBlend = SOFT_FILTER_BLEND; |
| 989 | + return; |
| 990 | + } |
| 991 | + |
| 992 | + String normalizedFilter = textureFilter.trim(); |
| 993 | + Float numericBlend = parseSoftFilterBlend(normalizedFilter); |
| 994 | + if (numericBlend != null) { |
| 995 | + screenFilterMode = ScreenFilterMode.SOFT; |
| 996 | + softFilterBlend = numericBlend; |
| 997 | + } else if ("nearest".equalsIgnoreCase(normalizedFilter)) { |
| 998 | + screenFilterMode = ScreenFilterMode.NEAREST; |
| 999 | + softFilterBlend = SOFT_FILTER_BLEND; |
| 1000 | + } else { |
| 1001 | + screenFilterMode = ScreenFilterMode.SOFT; |
| 1002 | + softFilterBlend = SOFT_FILTER_BLEND; |
| 1003 | + } |
| 1004 | + } |
| 1005 | + |
| 1006 | + private Float parseSoftFilterBlend(String textureFilter) { |
| 1007 | + try { |
| 1008 | + float blend = Float.parseFloat(textureFilter); |
| 1009 | + if ((blend >= 0.0f) && (blend <= 1.0f)) { |
| 1010 | + return blend; |
| 1011 | + } |
| 1012 | + } catch (NumberFormatException ignored) { |
| 1013 | + // Not a numeric blend value; fall back to named filter modes. |
| 1014 | + } |
| 1015 | + return null; |
| 1016 | + } |
| 1017 | + |
| 1018 | + private ScreenFilterMode getActiveScreenFilterMode() { |
| 1019 | + if ((screenFilterMode == ScreenFilterMode.SOFT) && (softFilterShader == null)) { |
| 1020 | + return ScreenFilterMode.LINEAR; |
| 1021 | + } |
| 1022 | + return screenFilterMode; |
| 1023 | + } |
| 1024 | + |
| 1025 | + private int getTextureOffset(ScreenFilterMode filterMode) { |
| 1026 | + return (filterMode == ScreenFilterMode.LINEAR) ? 3 : 0; |
| 1027 | + } |
| 1028 | + |
| 1029 | + private ShaderProgram createSoftFilterShader() { |
| 1030 | + ShaderProgram shader = new ShaderProgram(SOFT_FILTER_VERTEX_SHADER, SOFT_FILTER_FRAGMENT_SHADER); |
| 1031 | + if (!shader.isCompiled()) { |
| 1032 | + Gdx.app.error("MachineScreen", "Unable to compile soft filter shader: " + shader.getLog()); |
| 1033 | + shader.dispose(); |
| 1034 | + return null; |
| 1035 | + } |
| 1036 | + return shader; |
907 | 1037 | } |
908 | 1038 |
|
909 | 1039 | /** |
|
0 commit comments