You can play some games like using multiple partitions and keeping the part of your code that you're not constantly changing (e.g. resources) in another partition that you don't reload on each build. For example, put your .png files in a LittleFS and just don't keep reloading them all the time.
An appreciable part of that time is the sector erasing of the flash (remember, you can only set bits but not clear them - or vice versa - so you really do have to do a sector clear on the entire flash when reprogramming) so tinkering with bit rate just doesn't help much beyond 921.600 or so. There are combinations that claim to do 2mbps, but the additional spaces "between the bits" dominates the time so it's not like it really doubles the experience.
On other SOCs, I've had good luck using JTAG to just blast the image right into RAM but that's pretty fragile if you have code that normally stores constant (read-only) data in flash. It's probably possible to fiddle with the ilnker scripts and the
JTAG config and create such an environment (esp. if you have something like an 8MB OSPI part where RAM is fast and "plentiful") but at some point, you're not running what you're actually shipping so you have to be cautious about exotic build configurations. I've not tried it on ESP32, but it's definitely possible on other SOCs I've used. If you're used to other systems where everything is stored in flash and then copied to RAM to boot and run (e.g. IDT/MIPS, most embedded RISC-V parts, or, I think, STM32) you basically "just" need to create the second half of the memory configuration after the big memcpy and before the jump to the RAM image you just created/copied/uncompressed.
The Legacy ESP32s also have a landmine of goofy rules about what memory can be used for what purposes, so defining the right linker maps might not be trivial.
Depending on the type of development you're doing, it might also be possible to use the
QEMU simulator and run your code on a beefy virtualized host, but if you're doing any real amount of hardware twiddling - which is kind of the point of these parts - I just can't imagine that working well. Here's your chance to earn valuable internet points and be the hero of your peers!
Regardless if you implement the 'copy to memory' mode or not, to make it rock, you really have to get out of erasing and rewriting flash as much as you can. There may be an awesome tutorial on how to do this, but a cursory search - even thinking I know most of the key words - doesn't turn up a great HOWTO on this topic, so there's probably an opportunity to improve the ESP32 world if you'd sit down for a weekend and really work through it all and document it for the world's ESP32 developers.
Personally, I just try to write as much C++ as I can that I can unit test on a Real Computer so when I build it for ESP32, I then have some reasonable expectations of not being in a save-build-run loop.