@@ -31,7 +31,7 @@ void WGDefault::genChunk(Swan::WorldPlane &plane, Swan::Chunk &chunk) { | |||
Swan::TilePos pos(tilex, tiley); | |||
Swan::Chunk::RelPos rel(cx, cy); | |||
chunk.setTileID(rel, genTile(pos)); | |||
chunk.setTileData(rel, genTile(pos)); | |||
} | |||
} | |||
} |
@@ -71,7 +71,7 @@ void EntPlayer::tick(const Swan::Context &ctx, float dt) { | |||
.squareLength(); | |||
if (squared_dist < 0.5 * 0.5) { | |||
Swan::info << "Will pick up item at " << ent->getBody().getBounds().center() << "..."; | |||
// TODO: Pick up | |||
} | |||
} | |||
} |
@@ -19,17 +19,16 @@ public: | |||
Chunk(ChunkPos pos): pos_(pos) { | |||
data_.reset(new uint8_t[CHUNK_WIDTH * CHUNK_HEIGHT * sizeof(Tile::ID)]); | |||
visuals_.reset(new Visuals()); | |||
} | |||
Tile::ID *getTileData(); | |||
Tile::ID getTileID(RelPos pos); | |||
void setTileID(RelPos pos, Tile::ID id); | |||
void drawBlock(RelPos pos, const Tile &t); | |||
void setTileID(RelPos pos, Tile::ID id, SDL_Texture *tex); | |||
void setTileData(RelPos pos, Tile::ID id); | |||
void render(const Context &ctx, SDL_Renderer *rnd); | |||
void compress(); | |||
void decompress(); | |||
void render(const Context &ctx); | |||
void draw(const Context &ctx, Win &win); | |||
void tick(float dt); | |||
@@ -42,18 +41,18 @@ private: | |||
static constexpr float DEACTIVATE_INTERVAL = 20; | |||
static uint8_t *renderbuf; | |||
void renderList(SDL_Renderer *rnd); | |||
bool isCompressed() { return compressed_size_ != -1; } | |||
std::unique_ptr<uint8_t[]> data_; | |||
std::vector<std::pair<RelPos, SDL_Texture *>> draw_list_; | |||
ssize_t compressed_size_ = -1; // -1 if not compressed, a positive number if compressed | |||
bool need_render_ = false; | |||
float deactivate_timer_ = DEACTIVATE_INTERVAL; | |||
struct Visuals { | |||
CPtr<SDL_Texture, SDL_DestroyTexture> texture_; | |||
}; | |||
std::unique_ptr<Visuals> visuals_; | |||
CPtr<SDL_Texture, SDL_DestroyTexture> texture_; | |||
}; | |||
} |
@@ -29,7 +29,11 @@ public: | |||
} | |||
friend std::ostream &operator<<(std::ostream &os, const RTClock &clock) { | |||
os << (double)clock.duration() << 's'; | |||
double dur = clock.duration(); | |||
if (dur > 1) | |||
os << dur << 's'; | |||
else | |||
os << dur * 1000.0 << "ms"; | |||
return os; | |||
} | |||
@@ -31,6 +31,14 @@ struct Vector2 { | |||
return Vector2<T>(x > 0 ? 1 : -1, y > 0 ? 1 : -1); | |||
} | |||
constexpr Vector2<T> scale(T sx, T sy) { | |||
return Vector2<T>(x * sx, y * sy); | |||
} | |||
constexpr Vector2<T> scale(T s) { | |||
return scale(s, s); | |||
} | |||
constexpr operator std::pair<T, T>() const { | |||
return std::pair<T, T>(x, y); | |||
} |
@@ -1,6 +1,7 @@ | |||
#pragma once | |||
#include <vector> | |||
#include <deque> | |||
#include <utility> | |||
#include <memory> | |||
#include <map> | |||
@@ -19,7 +20,7 @@ namespace Swan { | |||
class World; | |||
class Game; | |||
class WorldPlane { | |||
class WorldPlane: NonCopyable { | |||
public: | |||
using ID = uint16_t; | |||
@@ -66,6 +67,8 @@ private: | |||
std::map<std::pair<int, int>, Chunk> chunks_; | |||
std::set<Chunk *> active_chunks_; | |||
std::vector<std::unique_ptr<Entity>> entities_; | |||
std::deque<Chunk *> chunk_init_list_; | |||
std::vector<std::unique_ptr<Entity>> spawn_list_; | |||
std::vector<Entity *> despawn_list_; | |||
std::vector<TilePos> debug_boxes_; |
@@ -12,6 +12,22 @@ inline std::ostream &operator<<(std::ostream &os, const SDL_Rect &rect) { | |||
return os; | |||
} | |||
class RenderTarget: NonCopyable { | |||
public: | |||
RenderTarget(SDL_Renderer *rnd, SDL_Texture *tex): rnd_(rnd) { | |||
prev_target_ = SDL_GetRenderTarget(rnd_); | |||
SDL_SetRenderTarget(rnd_, tex); | |||
} | |||
~RenderTarget() { | |||
SDL_SetRenderTarget(rnd_, prev_target_); | |||
} | |||
private: | |||
SDL_Renderer *rnd_; | |||
SDL_Texture *prev_target_; | |||
}; | |||
class TexLock: NonCopyable { | |||
public: | |||
TexLock(SDL_Texture *tex, SDL_Rect *rect = nullptr); |
@@ -23,19 +23,14 @@ Tile::ID Chunk::getTileID(RelPos pos) { | |||
return getTileData()[pos.y * CHUNK_WIDTH + pos.x]; | |||
} | |||
void Chunk::setTileID(RelPos pos, Tile::ID id) { | |||
void Chunk::setTileID(RelPos pos, Tile::ID id, SDL_Texture *tex) { | |||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | |||
draw_list_.push_back({ pos, tex }); | |||
} | |||
void Chunk::drawBlock(RelPos pos, const Tile &t) { | |||
keepActive(); | |||
SDL_Rect lockrect{ pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE }; | |||
TexLock lock(visuals_->texture_.get(), &lockrect); | |||
SDL_Rect srcrect{ 0, 0, t.image_.surface_->w, t.image_.surface_->h }; | |||
SDL_Rect destrect{ 0, 0, TILE_SIZE, TILE_SIZE }; | |||
lock.blit(&destrect, t.image_.surface_.get(), &srcrect); | |||
void Chunk::setTileData(RelPos pos, Tile::ID id) { | |||
getTileData()[pos.y * CHUNK_WIDTH + pos.x] = id; | |||
need_render_ = true; | |||
} | |||
void Chunk::compress() { | |||
@@ -56,7 +51,7 @@ void Chunk::compress() { | |||
data_.reset(new uint8_t[destlen]); | |||
memcpy(data_.get(), dest, destlen); | |||
visuals_.reset(); | |||
texture_.reset(); | |||
compressed_size_ = destlen; | |||
info | |||
@@ -88,8 +83,6 @@ void Chunk::decompress() { | |||
} | |||
data_ = std::move(dest); | |||
visuals_.reset(new Visuals()); | |||
need_render_ = true; | |||
info | |||
@@ -99,22 +92,21 @@ void Chunk::decompress() { | |||
compressed_size_ = -1; | |||
} | |||
void Chunk::render(const Context &ctx) { | |||
void Chunk::render(const Context &ctx, SDL_Renderer *rnd) { | |||
// The texture might not be created yet | |||
if (!visuals_->texture_) { | |||
visuals_->texture_.reset(SDL_CreateTexture( | |||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, | |||
if (!texture_) { | |||
texture_.reset(SDL_CreateTexture( | |||
ctx.game.win_.renderer_, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, | |||
CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE)); | |||
} | |||
// We wanna render directly to the texture | |||
RenderTarget target(rnd, texture_.get()); | |||
// We're caching tiles so we don't have to world.getTileByID() every time | |||
Tile::ID prevID = Tile::INVALID_ID; | |||
Tile *tile = ctx.game.invalid_tile_.get(); | |||
// Locking the texture lets us write to its piexls | |||
TexLock lock(visuals_->texture_.get()); | |||
for (int y = 0; y < CHUNK_HEIGHT; ++y) { | |||
for (int x = 0; x < CHUNK_WIDTH; ++x) { | |||
Tile::ID id = getTileID(RelPos(x, y)); | |||
@@ -123,31 +115,42 @@ void Chunk::render(const Context &ctx) { | |||
tile = &ctx.world.getTileByID(id); | |||
} | |||
// Find the source surface and rect... | |||
auto &srcsurf = tile->image_.surface_; | |||
SDL_Rect srcrect{ 0, 0, srcsurf->w, srcsurf->h }; | |||
// ...and blit it to the appropriate place | |||
SDL_Rect destrect{ x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE }; | |||
if (lock.blit(&destrect, srcsurf.get(), &srcrect) < 0) | |||
warn << "Failed to blit surface: " << SDL_GetError(); | |||
SDL_Rect dest{x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | |||
SDL_RenderCopy(rnd, tile->image_.texture_.get(), nullptr, &dest); | |||
} | |||
} | |||
need_render_ = false; | |||
} | |||
void Chunk::renderList(SDL_Renderer *rnd) { | |||
// Here, we know that the texture is created. | |||
// We still wanna render directly to the target texture | |||
RenderTarget target(rnd, texture_.get()); | |||
for (auto &[pos, tex]: draw_list_) { | |||
SDL_Rect dest{pos.x * TILE_SIZE, pos.y * TILE_SIZE, TILE_SIZE, TILE_SIZE}; | |||
SDL_RenderCopy(rnd, tex, nullptr, &dest); | |||
} | |||
} | |||
void Chunk::draw(const Context &ctx, Win &win) { | |||
if (isCompressed()) | |||
return; | |||
if (need_render_) { | |||
render(ctx); | |||
need_render_ = false; | |||
// The world plane is responsible for managing initial renders | |||
if (need_render_) | |||
return; | |||
if (draw_list_.size() > 0) { | |||
renderList(win.renderer_); | |||
draw_list_.clear(); | |||
} | |||
SDL_Rect rect{ 0, 0, CHUNK_WIDTH * TILE_SIZE, CHUNK_HEIGHT * TILE_SIZE }; | |||
win.showTexture( | |||
pos_ * Vec2i(CHUNK_WIDTH, CHUNK_HEIGHT), | |||
visuals_->texture_.get(), &rect); | |||
texture_.get(), &rect); | |||
} | |||
void Chunk::tick(float dt) { |
@@ -7,18 +7,11 @@ | |||
namespace Swan { | |||
static bool chunkLine(int l, WorldPlane &plane, ChunkPos &abspos, const Vec2i &dir, RTClock &clock) { | |||
static void chunkLine(int l, WorldPlane &plane, ChunkPos &abspos, const Vec2i &dir) { | |||
for (int i = 0; i < l; ++i) { | |||
plane.getChunk(abspos); | |||
// Don't blow our frame budget on generating chunks, | |||
// but generate as many as possible within the budget | |||
if (clock.duration() > 1 / 120.0) | |||
return true; | |||
abspos += dir; | |||
} | |||
return false; | |||
} | |||
World::World(Game *game, unsigned long rand_seed): | |||
@@ -38,11 +31,11 @@ void World::ChunkRenderer::tick(WorldPlane &plane, ChunkPos abspos) { | |||
RTClock clock; | |||
for (int i = 0; i < 4; ++i) { | |||
if (chunkLine(l, plane, abspos, Vec2i(0, -1), clock)) break; | |||
if (chunkLine(l, plane, abspos, Vec2i(1, 0), clock)) break; | |||
chunkLine(l, plane, abspos, Vec2i(0, -1)); | |||
chunkLine(l, plane, abspos, Vec2i(1, 0)); | |||
l += 1; | |||
if (chunkLine(l, plane, abspos, Vec2i(0, 1), clock)) break; | |||
if (chunkLine(l, plane, abspos, Vec2i(-1, 0), clock)) break; | |||
chunkLine(l, plane, abspos, Vec2i(0, 1)); | |||
chunkLine(l, plane, abspos, Vec2i(-1, 0)); | |||
l += 1; | |||
} | |||
} | |||
@@ -97,7 +90,7 @@ WorldPlane &World::addPlane(const std::string &gen) { | |||
WorldGen::Factory *factory = it->second; | |||
WorldGen *g = factory->create(*this); | |||
planes_.push_back(WorldPlane(id, this, std::shared_ptr<WorldGen>(g))); | |||
planes_.emplace_back(id, this, std::shared_ptr<WorldGen>(g)); | |||
return planes_[id]; | |||
} | |||
@@ -147,8 +140,8 @@ void World::tick(float dt) { | |||
auto bounds = player_->getBody().getBounds(); | |||
chunk_renderer_.tick( | |||
planes_[current_plane_], | |||
ChunkPos((int)bounds.pos.x / CHUNK_WIDTH, (int)bounds.pos.y / CHUNK_HEIGHT)); | |||
planes_[current_plane_], | |||
ChunkPos((int)bounds.pos.x / CHUNK_WIDTH, (int)bounds.pos.y / CHUNK_HEIGHT)); | |||
} | |||
} |
@@ -68,9 +68,10 @@ Chunk &WorldPlane::getChunk(ChunkPos pos) { | |||
gen_->genChunk(*this, chunk); | |||
active_chunks_.insert(&chunk); | |||
chunk.render(getContext()); | |||
chunk_init_list_.push_back(&chunk); | |||
} else if (iter->second.keepActive()) { | |||
active_chunks_.insert(&iter->second); | |||
chunk_init_list_.push_back(&iter->second); | |||
} | |||
return iter->second; | |||
@@ -79,11 +80,13 @@ Chunk &WorldPlane::getChunk(ChunkPos pos) { | |||
void WorldPlane::setTileID(TilePos pos, Tile::ID id) { | |||
Chunk &chunk = getChunk(chunkPos(pos)); | |||
Chunk::RelPos rp = relPos(pos); | |||
chunk.setTileID(rp, id); | |||
chunk.drawBlock(rp, world_->getTileByID(id)); | |||
if (active_chunks_.find(&chunk) == active_chunks_.end()) | |||
chunk.setTileID(rp, id, world_->getTileByID(id).image_.texture_.get()); | |||
if (active_chunks_.find(&chunk) == active_chunks_.end()) { | |||
active_chunks_.insert(&chunk); | |||
chunk_init_list_.push_back(&chunk); | |||
} | |||
} | |||
void WorldPlane::setTile(TilePos pos, const std::string &name) { | |||
@@ -156,6 +159,14 @@ void WorldPlane::draw(Win &win) { | |||
(int)floor(pbounds.pos.x / CHUNK_WIDTH), | |||
(int)floor(pbounds.pos.y / CHUNK_HEIGHT)); | |||
// Just init one chunk per frame | |||
if (chunk_init_list_.size() > 0) { | |||
Chunk *chunk = chunk_init_list_.front(); | |||
info << "render chunk " << chunk->pos_; | |||
chunk_init_list_.pop_front(); | |||
chunk->render(getContext(), win.renderer_); | |||
} | |||
for (int x = -1; x <= 1; ++x) { | |||
for (int y = -1; y <= 1; ++y) { | |||
auto iter = chunks_.find(pcpos + ChunkPos(x, y)); |
@@ -202,6 +202,10 @@ int main(int argc, char **argv) { | |||
slow_frames = 0; | |||
} | |||
if (dt > 1/59.0) { | |||
warn << "Dropped below 60 FPS: " << 1 / dt; | |||
} | |||
// Simple case: we can keep up, only need one physics update | |||
RTClock update_clock; | |||
if (dt <= 1 / 25.0) { |