This is a really interesting question. Clearly sending a 60FPS stream of 720p gameplay (uncompressed!) is going to be out of the question, but there are a few straight-forward strategies for reducing the bandwidth of images in GameMaker. Here's what immediately springs to mind:
- Scale down your surface to 1/2 width & 1/2 height (which is 1/4 area and therefore 1/4 bytes)
- Update the surface every third frame, dropping you down to 20FPS
- Use the buffer compression functions (which are lossless)
As people have mentioned, 720p at 60FPS is 211mb/s. With the first two strategies, which reduce amount of data being transmitted by 12x, that drops down to 18mb/s which is... more reasonable. With compression in there, you can probably get that down to around 15mb/s which
might be doable. This does mean people will see the game blurry as hell and at a 20FPS in 360p which... might not be what you're after! I also don't know if buffer compression is going to be fast enough to keep up - ideally we'd use a thread for that but no dice in GM at this time.
There's a bit of annoyance here insofar that for what you're trying to do you probably have no interest in the alpha channel of your surface. GM doesn't offer a way to render to 24-bit image buffers otherwise you could shave off 25% of your transmission size simply by ignoring the alpha channel. I'd imagine buffer compression would recognise the alpha channel and mostly compress that away, but it's wasted time and space regardless.
If you're making a pixel art game with scaling then you can use other compression strategies. If you're natively rendering at 360p and scaling up to 720p then you can skip the scaling down part of the process and maintain a nice clear image. If your game has a limited colour palette, up to 256 colours all at 100% alpha, then you can use the 32-bit nature of GM's surfaces to your advantage. You can make a compression system that turns a 2x2 block of input pixels into a single 4x8-bit output pixel, and you can also write that in a shader which is parallelised and faster than a CPU-side compression algo. With a cleverly designed LUT texture, you can achieve a 4:1 compression ratio with a modest performance penalty if you accept the aforementioned 256 colour limitation. If you have a 360p pixel art game and you're compressing it in this way, you can run at 30FPS with a bandwidth of 6.6mb/s which is within reach of some (but by no means all) consumer connections.
At the end of the day, this is mostly academic. You shouldn't really be trying to send packets substantially larger than 1kb (Ethernet has a maximum transmission unit of 1518 bytes which usually acts as an upper limit on the realistic packet size) so a 6.6mb/s stream will require thousands upon thousands of packets to be handled every frame. Additionally, although a connection may be capable of a given bandwidth, that doesn't mean those packets are going to arrive smoothly or consistently. Running a local simulation is always going to achieve a better experience because the client can fill in the blanks if packets arrive in fits and starts. This is where the bulk of multiplayer programming is, and it's for good reason.
As a closing remark, if you don't want to run an
actual simulation on the client then, instead of sending the entire surface, you can send information on what the draw calls are. This will rather severely limit what kind of draw calls you can make and will incur a substantial performance penalty on the server, but with some imaginative thinking I reckon you could achieve some impressive results. This would mean caching all the relevant draw calls for a single frame and sending those over as a set of instructions to be executed on the client e.g.
draw_sprite_ext()
will get boiled down to a few integers and floats which represent a
draw_sprite_ext()
call that the client can then execute. This is effectively a one-way
remote procedure call, which is a topic all of its own that I encourage you to look into.