Skip to content

Commit 1eaf114

Browse files
committed
Added softness shader, to allow fractional filter values between nearest and linear. Defaults to 0.4, slightly closer to nearest.
1 parent 0afae9f commit 1eaf114

1 file changed

Lines changed: 144 additions & 14 deletions

File tree

core/src/main/java/emu/jvic/MachineScreen.java

Lines changed: 144 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.badlogic.gdx.graphics.Texture;
1616
import com.badlogic.gdx.graphics.g2d.BitmapFont;
1717
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
18+
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
1819
import com.badlogic.gdx.scenes.scene2d.InputEvent;
1920
import com.badlogic.gdx.scenes.scene2d.Stage;
2021
import com.badlogic.gdx.scenes.scene2d.ui.Image;
@@ -39,6 +40,47 @@
3940
*/
4041
public class MachineScreen implements Screen {
4142

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+
4284
/**
4385
* The Game object for JVic. Allows us to easily change screens.
4486
*/
@@ -72,9 +114,13 @@ public class MachineScreen implements Screen {
72114
private ExtendViewport viewport;
73115
private Camera camera;
74116
private Texture[] screens;
117+
75118
private int drawScreen = 1;
76119
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;
78124

79125
// Screen resources for each MachineType.
80126
private Map<MachineType, Pixmap> machineTypePixmaps;
@@ -149,6 +195,7 @@ public MachineScreen(JVic jvic, JVicRunner jvicRunner, DialogHandler dialogHandl
149195
jvicRunner.init(this, machineType.getTotalScreenWidth(), machineType.getTotalScreenHeight());
150196

151197
batch = new SpriteBatch();
198+
softFilterShader = createSoftFilterShader();
152199

153200
machineTypePixmaps = new HashMap<MachineType, Pixmap>();
154201
machineTypeTextures = new HashMap<MachineType, Texture[]>();
@@ -205,7 +252,7 @@ public MachineScreen(JVic jvic, JVicRunner jvicRunner, DialogHandler dialogHandl
205252

206253
// If the application is running on mobile web, then show FPS enabled, but
207254
// for desktop web, turn off by default.
208-
showFPS = jvicRunner.isMobile();
255+
//showFPS = jvicRunner.isMobile();
209256

210257
// FPS font
211258
font = new BitmapFont(Gdx.files.internal("data/default.fnt"), false);
@@ -264,12 +311,8 @@ public void initMachine(AppConfigItem appConfigItem, boolean startedByUser) {
264311
drawScreen = 1;
265312
updateScreen = 0;
266313

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();
273316
}
274317

275318
private void activateScreenResources(MachineType machineType) {
@@ -386,11 +429,24 @@ public void render(float delta) {
386429
public boolean copyPixels() {
387430
ensureActiveScreenResources();
388431
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+
}
390437
updateScreen = (updateScreen + 1) % 3;
391438
drawScreen = (drawScreen + 1) % 3;
392439
return true;
393440
}
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+
}
394450

395451
private void draw(float delta) {
396452
// Get the KeyboardType currently being used by the MachineScreenProcessor.
@@ -452,18 +508,33 @@ private void draw(float delta) {
452508
camera.update();
453509
batch.setProjectionMatrix(camera.combined);
454510
batch.disableBlending();
511+
ScreenFilterMode activeFilterMode = getActiveScreenFilterMode();
512+
if (activeFilterMode == ScreenFilterMode.SOFT) {
513+
batch.setShader(softFilterShader);
514+
}
455515
batch.begin();
456516
Color c = batch.getColor();
457517
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+
}
458526

459527
// Texture isn't always drawn to match physical pixels.
460528
batch.draw(
461-
screens[drawScreen + textureOffset],
529+
screens[drawScreen + getTextureOffset(activeFilterMode)],
462530
0, 0, renderWidth, renderHeight,
463531
machineType.getHorizontalOffset(), machineType.getVerticalOffset(),
464532
machineType.getVisibleScreenWidth(), machineType.getVisibleScreenHeight(),
465533
false, false);
466534
batch.end();
535+
if (activeFilterMode == ScreenFilterMode.SOFT) {
536+
batch.setShader(null);
537+
}
467538

468539
// Render the UI elements, e.g. the keyboard and joystick icons.
469540
viewportManager.getCurrentCamera().update();
@@ -542,9 +613,7 @@ private void draw(float delta) {
542613

543614
if (showFPS) {
544615
StringBuilder overlayText = new StringBuilder();
545-
overlayText.append("FPS: ");
546-
overlayText.append(Gdx.graphics.getFramesPerSecond());
547-
616+
548617
String performanceStats = jvicRunner.getPerformanceStatsText();
549618
if ((performanceStats != null) && !performanceStats.isEmpty()) {
550619
overlayText.append('\n');
@@ -875,6 +944,9 @@ public void dispose() {
875944
screenSizeIcon.dispose();
876945
warpSpeedIcon.dispose();
877946
cameraIcon.dispose();
947+
if (softFilterShader != null) {
948+
softFilterShader.dispose();
949+
}
878950
batch.dispose();
879951
jvicRunner.stop();
880952
disposeScreens();
@@ -903,7 +975,65 @@ private void disposeScreens() {
903975
* @param blurOn
904976
*/
905977
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;
9071037
}
9081038

9091039
/**

0 commit comments

Comments
 (0)