[emscripten] Web build
I'm working on wasm port.
Demo is available here https://turch.in/rttr/index.html
Right now it's not playable. As emscripten do not support socket listen in web environment.
And I need good advice: how do you (settlers freaks) see implementation of local connection without sockets?
@Flamefire well, motivation is to get original fill and look. First I ported widelands, playable version is here https://turch.in/widelands/ . But it's a bit different game... Besides, it's cool to have version of the game which doesn't require download and install, it just works right now and here in browser. Yes there are performance issues, wasm is executed in 32bit vm with jit, but I think it's not a big problem as machines gets faster(slower than before, but steady) and wasm vm soon may get fat performance boost with upcoming updates. I will go through comments later and add fixes. First I want to consider workaround for network problem. I agree that adding of custom logic for local game is bad idea in matter of maintenatability. There few options I can try: it's time to write own websocket-tcp/udp proxy, hack libwebsocket.js to make virtual peer connections for localhost.
Well, experiment with hacking of libsock.js was successful + I had to slightly modify game network code: recv messages in complete chunks, without waiting for any additional blocks.
But @Flamefire you are right, ingame performance is too poor even with aggressive optimisations. Do you have any ideas how to fix that?
It seems like you have the same problem with the graphics/shadows as my Android port of rttr. Do you know why this happens? Would help me a lot :D
But @Flamefire you are right, ingame performance is too poor even with aggressive optimisations. Do you have any ideas how to fix that?
TBH: The core code isn't exactly optimized for performance. There are a lot of indirections and virtual calls leading to pointer-chasing and poor branch prediction.
But the code has to work at all and be easy enough to follow to verify it is always in sync for all players. I.e. the simulation must be in lock-step which was the main focus with performance being "good enough" except for large maps and road networks where the pathfinding starts to be a bottleneck.
I don't think there is an easy way to fix this without rewriting a lot of the code which then might reintroduce especially all the async bugs we fixed over the years. Although there are some targeted optimization opportunities like one recently fixed in the pathfinding code: https://github.com/Return-To-The-Roots/s25client/pull/1734
It seems like you have the same problem with the graphics/shadows as my Android port of rttr. Do you know why this happens? Would help me a lot :D
There must something related to textures format/internal format. I'm looking at TerrainRenderer and something tells me that 99% of its code can be done by shaders.
There must something related to textures format/internal format. I'm looking at TerrainRenderer and something tells me that 99% of its code can be done by shaders.
I started with introducing shaders a long time ago but never got around finishing it. My first point was about handling player textures by the shader instead of the bitmap class. I.e. have the shader combine the main texture and color the masked overlay.
And yes the TerrainRenderer is mighty tricky. There are a few tricks already used to make rendering as efficient as possible grouping stuff. A lot might also be gained by caching part of the scenes. E.g. the terrain is static as long as the view isn't scrolled or height levels are adjusted. I introduced listeners to make this feasible but again didn't get around using them for this especially due to the existing code complexity
Well, if I had planning a renderer for terrain there must be one big texture atlas with terrain sprites, generated texture with tiles indexes from atlas + gl program which accepts both 2d samples and by pixel copies atlas sprites to it's onscreen positions using mapping from indexes texture. Then in same way apply gl program to render dither between terrain connections water/land greenland/winter etc. Then gl program to render roads, then objects. Not sure when, but I will try to implement it.
Well, if I had planning a renderer for terrain there must be one big texture atlas with terrain sprites, generated texture with tiles indexes from atlas + gl program which accepts both 2d samples and by pixel copies atlas sprites to it's onscreen positions using mapping from indexes texture. Then in same way apply gl program to render dither between terrain connections water/land greenland/winter etc. Then gl program to render roads, then objects. Not sure when, but I will try to implement it.
We have the texture atlas already. The textures are given as positions/triangles inside that. That's actually inherited from S2.
One major point would also be support for animated textures: Water uses multiple parts of the texture and in some terrains there is e.g. lave doing that too.
If you get some minimal program working just for the terrain textures and transitions that includes the animation I'd be very curious to see that :-)
It seems like you have the same problem with the graphics/shadows as my Android port of rttr. Do you know why this happens? Would help me a lot :D
There must something related to textures format/internal format. I'm looking at TerrainRenderer and something tells me that 99% of its code can be done by shaders.
I've fixed the shading issues. I don't know if this works for your web port but here are the changes(just the few in the terrain renderer)
https://github.com/Farmer-Markus/s25rttr-android/blob/main/patch%2Fs25client.patch#L2207
@Farmer-Markus awesome, I will try. Currently my only progress is ruining of terrain rendering completely and attempt to show at least something.
What I'm working on:
precision highp float;
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
varying vec2 vTextureCoord;
uniform vec4 inputSize;
uniform vec4 outputFrame;
vec4 filterVertexPosition (void) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.)).xy, 0., 1.);
}
vec2 filterTextureCoord (void) {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
void main (void) {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord(); // get texture coord
}
and main magic happens in fragment shader
precision highp float;
varying vec2 vTextureCoord;
uniform vec4 outputFrame;
uniform vec4 inputSize;
uniform sampler2D uAtlas; // really big texture with all loaded sprites
uniform vec2 uAtlasSize; // it's size
uniform vec2 uTileSize; // for now supports only fixed tile size
uniform vec2 uOutSize;// "real" output size
uniform sampler2D uMapping; // texture where we store in "zw" vec2 coords of sprite in atlas
uniform vec2 uMappingSize; // mapping texture size
uniform vec2 uBackgroundSize; // background size in map points
uniform vec2 uBackgroundOffset; // background offset in pixels
void main () {
// copy current pixel color from atlas using mapping texture
vec2 uv = vTextureCoord/outputFrame.zw*inputSize.xy;//remap to [0..1] as output depends on "inner" tileset
vec2 tileAtlas = uTileSize/uAtlasSize;
vec2 screenTile = (uv*uOutSize+uBackgroundOffset)/uTileSize;
vec2 tileCoord = floor(screenTile); // index in mapping sample
vec2 tileInnerCoord = fract(screenTile)*tileAtlas; // relative coords
float mappingIndex = tileCoord.y*uBackgroundSize.x+tileCoord.x;
float mappingY = floor(mappingIndex/uMappingSize.x);
float mappingX = mappingIndex-mappingY*uMappingSize.x;
vec2 shapeCoord = vec2(mappingX, mappingY)/uMappingSize;
// here we finally found x,y of sprite in atlas texture
vec2 tileAtlasCoord = 255.0*texture2D(uMapping, shapeCoord).xy*tileAtlas;
// finally output pixel color using
gl_FragColor = texture2D(uAtlas, tileAtlasCoord+tileInnerCoord);
}
nothing really to show yet, but I fill that it might and will work.
Looks better, runs faster. Will perform some cleanups, add save/load functionality and update PR I think my changes will affect native app performance as well.
Great work! I also assume it will improve the native app.
Did you get the animations for water and lava working with the shader?
If so can you factor out the changes to the rendering to a PR separate from the emscripten changes? Besides having more manageable PRs/changesets I'm not fully convinced about emscripten. So for me that would depend on how intrusive the changes required are and of course what @Flow86 thinks about it.
But as mentioned: I'd really love to have a shader based renderer in any case
Looks better, runs faster. Will perform some cleanups, add save/load functionality and update PR I think my changes will affect native app performance as well.
Great work! I also assume it will improve the native app.
Did you get the animations for water and lava working with the shader? If so can you factor out the changes to the rendering to a PR separate from the emscripten changes? Besides having more manageable PRs/changesets I'm not fully convinced about emscripten. So for me that would depend on how intrusive the changes required are and of course what @Flow86 thinks about it.
But as mentioned: I'd really love to have a shader based renderer in any case
I'd love shader based renderer, maybe that'd help improving the lightning and would be closer to the original :)
ok, maybe my update will be a bit disappointing, but I overestimated my dev capabilities to impose such significant refactoring like rendering via gl program. The good stuff is that I noticed bottleneck in rendering loop while I was trying. And replacement of glDrawArrays with glBegin/glEnd did the magic. Minimum of changes with great result, I like it.
Regarding emscripten, there are a lot of bits to do. It requires lwebsocket.js library patching + you need standalone html/js project to run the build. In other words far from PR candidate.
The good stuff is that I noticed bottleneck in rendering loop while I was trying. And replacement of glDrawArrays with glBegin/glEnd did the magic. Minimum of changes with great result, I like it.
Sounds great! What exactly was that bottleneck?
As for that replacement: I'm actually surprised by that. The immediate mode (glBegin/glEnd) puts more stress on the CPU with the additional driver calls and is deprecated in OpenGL 3 and removed in OpenGL ES and partially(?) in OGL 3.1
I have found reports that glDrawArrays was not faster than immediate mode with explanations that this likely happens in environments doing software rendering, i.e. processing of the OpenGL pipeline is done partially/mostly on CPU so you don't gain much by batching the calls.
Especially as we have users using OpenGL ES we can't switch back to immediate mode.
However having this identified as a bottleneck is still very useful as it means we can improve our usage in this area, e.g. by not transmitting the data on every call but storing it server-side/on GPU.
We should benchmark the difference and see where / which objects benefit most of the change and see how to optimize those using the new API.
Well in my case I got performance improvement 5-6 fps (with empty map) -> 45-50fps (very tight road graph) after migration to immediate mode. Will try to check gl4es library internals, I'm using it as replacement of glad for emscripten build.
I guess we need to check if this is the same for the native builds or if the difference isn't there or opposite of the emscripten build.
Maybe there is some emulation of the OpenGL 3 APIs in the library onto the immediate mode pipeline?
Definitely there is emulation https://github.com/ptitSeb/gl4es/blob/a744af14d4afbda77bf472bc53f43b9ceba39cc0/src/gl/gl4es.c#L246