1 module gbaid.render.renderer; 2 3 import derelict.sdl2.sdl : SDL_ShowCursor, SDL_ENABLE, SDL_DISABLE; 4 5 import gbaid.render.gl; 6 import gbaid.render.gl20; 7 import gbaid.render.shader; 8 9 public class FrameRenderer { 10 private Context context = null; 11 private Program program = null; 12 private Texture texture = null; 13 private Program upscaleProgram = null; 14 private Texture upscaledTexture = null; 15 private FrameBuffer upscaleFrameBuffer = null; 16 private Texture outputTexture = null; 17 private VertexArray vertexArray = null; 18 private immutable uint frameWidth, frameHeight; 19 private int windowWidth, windowHeight; 20 private bool _fullScreen = false; 21 private bool _useVsync = false; 22 private FilteringMode filteringMode = FilteringMode.NONE; 23 private UpscalingMode upscalingMode = UpscalingMode.NONE; 24 25 public this(uint frameWidth, uint frameHeight) { 26 this.frameWidth = frameWidth; 27 this.frameHeight = frameHeight; 28 windowWidth = frameWidth; 29 windowHeight = frameHeight; 30 } 31 32 @property public void fullScreen(bool full) { 33 _fullScreen = full; 34 35 if (context !is null) { 36 SDL_ShowCursor(full ? SDL_DISABLE : SDL_ENABLE); 37 context.setFullScreen(full); 38 } 39 } 40 41 @property public void useVsync(bool use) { 42 _useVsync = use; 43 44 if (context !is null) { 45 context.enableVsync(use); 46 } 47 } 48 49 public void setScale(float scale) { 50 windowWidth = cast(uint) (frameWidth * scale + 0.5f); 51 windowHeight = cast(uint) (frameHeight * scale + 0.5f); 52 53 if (context !is null) { 54 context.setWindowSize(windowWidth, windowHeight); 55 } 56 } 57 58 public void setFilteringMode(FilteringMode mode) { 59 filteringMode = mode; 60 } 61 62 public void setUpscalingMode(UpscalingMode mode) { 63 upscalingMode = mode; 64 } 65 66 public void create() { 67 if (context !is null) { 68 return; 69 } 70 71 scope (failure) { 72 destroy(); 73 } 74 75 context = new GL20Context(); 76 context.setWindowTitle("GBAiD"); 77 context.setResizable(true); 78 context.setWindowSize(windowWidth, windowHeight); 79 context.setFullScreen(_fullScreen); 80 context.enableVsync(_useVsync); 81 context.create(); 82 context.enableCapability(CULL_FACE); 83 84 SDL_ShowCursor(_fullScreen ? SDL_DISABLE : SDL_ENABLE); 85 86 program = makeProgram(TEXTURE_POST_PROCESS_VERTEX_SHADER_SOURCE, WINDOW_OUTPUT_FRAGMENT_SHADER_SOURCE); 87 program.use(); 88 program.bindSampler(0); 89 90 texture = makeTexture(RGBA, RGB5_A1, frameWidth, frameHeight); 91 92 final switch (upscalingMode) with (UpscalingMode) { 93 case NONE: 94 upscaleProgram = null; 95 upscaledTexture = null; 96 upscaleFrameBuffer = null; 97 break; 98 case EPX: 99 upscaleProgram = makeProgram(TEXTURE_POST_PROCESS_VERTEX_SHADER_SOURCE, EPX_UPSCALE_FRAGMENT_SHADER_SOURCE); 100 upscaledTexture = makeTexture(RGBA, RGBA8, frameWidth * 2, frameHeight * 2); 101 break; 102 case XBR: 103 upscaleProgram = makeProgram(TEXTURE_POST_PROCESS_VERTEX_SHADER_SOURCE, XBR_UPSCALE_FRAGMENT_SHADER_SOURCE); 104 upscaledTexture = makeTexture(RGBA, RGBA8, frameWidth * 5, frameHeight * 5); 105 break; 106 case BICUBIC: 107 upscaleProgram = makeProgram(TEXTURE_POST_PROCESS_VERTEX_SHADER_SOURCE, BICUBIC_UPSCALE_FRAGMENT_SHADER_SOURCE); 108 int actualWidth, actualHeight; 109 context.getActualWindowSize(&actualWidth, &actualHeight); 110 upscaledTexture = makeTexture(RGBA, RGBA8, actualWidth, actualHeight); 111 texture.setWraps(CLAMP_TO_EDGE, CLAMP_TO_EDGE); 112 break; 113 } 114 if (upscaleProgram !is null) { 115 upscaleFrameBuffer = context.newFrameBuffer(); 116 upscaleFrameBuffer.create(); 117 upscaleFrameBuffer.attach(COLOR_ATTACHMENT0, upscaledTexture); 118 if (!upscaleFrameBuffer.isComplete()) { 119 throw new GLException("Upscale framebuffer is incomplete"); 120 } 121 122 upscaleProgram.use(); 123 upscaleProgram.bindSampler(0); 124 upscaleProgram.setUniform("size", frameWidth, frameHeight); 125 } 126 127 outputTexture = upscaleProgram !is null ? upscaledTexture : texture; 128 final switch (filteringMode) { 129 case FilteringMode.NONE: 130 outputTexture.setFilters(NEAREST, NEAREST); 131 break; 132 case FilteringMode.LINEAR: 133 outputTexture.setFilters(LINEAR, LINEAR); 134 break; 135 } 136 137 vertexArray = context.newVertexArray(); 138 vertexArray.create(); 139 vertexArray.setData(generatePlane(2, 2)); 140 } 141 142 public void destroy() { 143 if (context is null) { 144 return; 145 } 146 context.destroy(); 147 context = null; 148 program = null; 149 texture = null; 150 upscaleProgram = null; 151 upscaledTexture = null; 152 upscaleFrameBuffer = null; 153 outputTexture = null; 154 vertexArray = null; 155 } 156 157 public void draw(short[] frame) { 158 if (!context.isCreated()) { 159 create(); 160 } 161 162 context.getWindowSize(&windowWidth, &windowHeight); 163 texture.setImageData(cast(ubyte[]) frame, frameWidth, frameHeight); 164 texture.bind(0); 165 if (upscaleProgram !is null) { 166 upscaleProgram.use(); 167 upscaleFrameBuffer.bind(); 168 context.setViewPort(0, 0, upscaledTexture.getWidth(), upscaledTexture.getHeight()); 169 vertexArray.draw(); 170 upscaleFrameBuffer.unbind(); 171 upscaledTexture.bind(0); 172 } 173 program.use(); 174 program.setUniform("inputSize", frameWidth, frameHeight); 175 program.setUniform("outputSize", windowWidth, windowHeight); 176 context.setViewPort(0, 0, windowWidth, windowHeight); 177 vertexArray.draw(); 178 context.updateDisplay(); 179 } 180 181 public bool isCloseRequested() { 182 return context.isWindowCloseRequested(); 183 } 184 185 private Program makeProgram(string vertexShaderSource, string fragmentShaderSource) { 186 Shader vertexShader = context.newShader(); 187 vertexShader.create(); 188 vertexShader.setSource(new ShaderSource(vertexShaderSource, true)); 189 vertexShader.compile(); 190 Shader fragmentShader = context.newShader(); 191 fragmentShader.create(); 192 fragmentShader.setSource(new ShaderSource(fragmentShaderSource, true)); 193 fragmentShader.compile(); 194 Program program = context.newProgram(); 195 program.create(); 196 program.attachShader(vertexShader); 197 program.attachShader(fragmentShader); 198 program.link(); 199 return program; 200 } 201 202 private Texture makeTexture(Format format, InternalFormat internalFormat, int width, int height) { 203 Texture texture = context.newTexture(); 204 texture.create(); 205 texture.setFormat(format, internalFormat); 206 texture.setWraps(CLAMP_TO_BORDER, CLAMP_TO_BORDER); 207 texture.setBorderColor(0, 0, 0, 1); 208 texture.setFilters(NEAREST, NEAREST); 209 texture.setImageData(null, width, height); 210 return texture; 211 } 212 } 213 214 public enum FilteringMode { 215 NONE, 216 LINEAR 217 } 218 219 public enum UpscalingMode { 220 NONE, 221 EPX, 222 XBR, 223 BICUBIC 224 } 225 226 private VertexData generatePlane(float width, float height) { 227 width /= 2; 228 height /= 2; 229 VertexData vertexData = new VertexData(); 230 VertexAttribute positionsAttribute = new VertexAttribute("positions", FLOAT, 3); 231 vertexData.addAttribute(0, positionsAttribute); 232 float[] positions = [ 233 -width, -height, 0, 234 width, -height, 0, 235 -width, height, 0, 236 width, height, 0 237 ]; 238 positionsAttribute.setData(cast(ubyte[]) positions); 239 uint[] indices = [0, 3, 2, 0, 1, 3]; 240 vertexData.setIndices(indices); 241 return vertexData; 242 }