merge remote and local changes
This commit is contained in:
@@ -1,22 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
<<<<<<< HEAD:src/main.cpp
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
=======
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
>>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp
|
||||
#include "Vector2.h"
|
||||
#include "Vector3.h"
|
||||
#include "Vector4.h"
|
||||
#include "Matrix4x4.h"
|
||||
#include "MathUtil.h"
|
||||
#include "Color.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "Rasterizer.h"
|
||||
#include "TriangleRasterizer.h"
|
||||
#include "Triangle.h"
|
||||
#include "Camera.h"
|
||||
#include <cstdlib>
|
||||
#include "Timer.h"
|
||||
#include "Vertex.h"
|
||||
<<<<<<< HEAD:src/main.cpp
|
||||
#include "DepthBuffer.h"
|
||||
#include "SpriteAssetLoader.h"
|
||||
#include "SpriteRasterizer.h"
|
||||
@@ -25,8 +31,14 @@
|
||||
#include "AudioInput.h"
|
||||
#include "AudioOutput.h"
|
||||
#include "ButtonInput.h"
|
||||
=======
|
||||
#include "DrawContext.h"
|
||||
#include "test_sprite.h"
|
||||
#include "font_atlas.h"
|
||||
>>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp
|
||||
|
||||
#include "Display.h"
|
||||
#include "TimeSource.h"
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
#include "FBDisplay.h"
|
||||
#else
|
||||
@@ -115,6 +127,7 @@ static bool IsTriangleVisible(const CubeTriangle &triangle, const std::array<Mat
|
||||
return faceNormal.dot(faceCenter) > 0.0f;
|
||||
}
|
||||
|
||||
<<<<<<< HEAD:src/main.cpp
|
||||
static bool HasArg(int argc, char* argv[], const std::string& expected)
|
||||
{
|
||||
for (int i = 1; i < argc; ++i)
|
||||
@@ -253,14 +266,101 @@ static int RunTomGame()
|
||||
display->shutdown();
|
||||
delete display;
|
||||
return 0;
|
||||
=======
|
||||
static void PrintUsage(const char *program_name)
|
||||
{
|
||||
std::cout
|
||||
<< "Usage: " << program_name << " [--fps 30|45|60]\n"
|
||||
<< " " << program_name << " [--fps=30|45|60]\n";
|
||||
}
|
||||
|
||||
struct ProgramOptions
|
||||
{
|
||||
uint32_t target_fps;
|
||||
bool show_help;
|
||||
|
||||
ProgramOptions()
|
||||
: target_fps(Core::Timer::DefaultFps),
|
||||
show_help(false)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
static ProgramOptions ParseProgramOptions(int argc, char *argv[])
|
||||
{
|
||||
ProgramOptions options;
|
||||
|
||||
for (int i = 1; i < argc; ++i)
|
||||
{
|
||||
const char *arg = argv[i];
|
||||
const char *fps_value = nullptr;
|
||||
|
||||
if (std::strcmp(arg, "--help") == 0 || std::strcmp(arg, "-h") == 0)
|
||||
{
|
||||
options.show_help = true;
|
||||
return options;
|
||||
}
|
||||
else if (std::strcmp(arg, "--fps") == 0)
|
||||
{
|
||||
if (i + 1 < argc)
|
||||
{
|
||||
fps_value = argv[++i];
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Missing value for --fps, falling back to 30. Supported: 30, 45, 60.\n";
|
||||
}
|
||||
}
|
||||
else if (std::strncmp(arg, "--fps=", 6) == 0)
|
||||
{
|
||||
fps_value = arg + 6;
|
||||
}
|
||||
|
||||
if (fps_value != nullptr)
|
||||
{
|
||||
const uint32_t parsed_fps = static_cast<uint32_t>(std::strtoul(fps_value, nullptr, 10));
|
||||
if (Core::Timer::is_supported_fps(parsed_fps))
|
||||
{
|
||||
options.target_fps = parsed_fps;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Unsupported FPS '" << fps_value << "', falling back to 30. Supported: 30, 45, 60.\n";
|
||||
options.target_fps = Core::Timer::DefaultFps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
static void SleepRemainingFrameTime(const Core::Timer &timer, const Platform::ITimeSource &time_source)
|
||||
{
|
||||
const uint32_t sleep_ms = timer.remaining_frame_ms(time_source.get_time_ms());
|
||||
if (sleep_ms > 0u)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_ms));
|
||||
}
|
||||
>>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
<<<<<<< HEAD:src/main.cpp
|
||||
if (HasArg(argc, argv, "--tom"))
|
||||
{
|
||||
return RunTomGame();
|
||||
}
|
||||
=======
|
||||
const ProgramOptions options = ParseProgramOptions(argc, argv);
|
||||
if (options.show_help)
|
||||
{
|
||||
PrintUsage(argv[0]);
|
||||
return 0;
|
||||
}
|
||||
Core::Timer timer(options.target_fps);
|
||||
Platform::SteadyTimeSource time_source;
|
||||
>>>>>>> 777ff96602c52c86f03d612bb4213de746f580a5:src/Apps/Demo/main.cpp
|
||||
|
||||
#ifdef USE_FRAMEBUFFER
|
||||
Platform::IDisplay *display = new Platform::FBDisplay();
|
||||
@@ -274,10 +374,25 @@ int main(int argc, char *argv[])
|
||||
return -1;
|
||||
}
|
||||
|
||||
Core::FrameBuffer *frameBuffer = new Core::FrameBuffer(width, height);
|
||||
Core::DepthBuffer *depthBuffer = new Core::DepthBuffer(width, height);
|
||||
Rasterizer::Rasterizer rasterizer(frameBuffer, depthBuffer);
|
||||
Rasterizer::TriangleRasterizer triangleRasterizer(frameBuffer, depthBuffer);
|
||||
Gfx::DrawContext ctx(width, height);
|
||||
std::cout << "Target FPS: " << timer.target_fps() << std::endl;
|
||||
|
||||
RenderData::BitmapFont font;
|
||||
font.atlas = RenderData::Image(font_atlas_pixels, font_atlas_width, font_atlas_height);
|
||||
font.char_w = font_char_w;
|
||||
font.char_h = font_char_h;
|
||||
font.columns = font_columns;
|
||||
font.first_char = font_first_char;
|
||||
|
||||
RenderData::Image sprite_img(test_sprite_pixels, test_sprite_width, test_sprite_height, 0x00000000);
|
||||
RenderData::SpriteRegion sprite_region(&sprite_img, 0, 0, sprite_img.width, sprite_img.height);
|
||||
|
||||
const std::array<uint16_t, 8 * 4> tileIds = {
|
||||
0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile,
|
||||
RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0,
|
||||
0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile,
|
||||
RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0, RenderData::Tilemap::EmptyTile, 0};
|
||||
RenderData::Tilemap testTilemap(tileIds.data(), 8, 4, &sprite_img, sprite_img.width, sprite_img.height, 1);
|
||||
|
||||
Scene::Camera camera;
|
||||
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
|
||||
@@ -310,20 +425,31 @@ int main(int argc, char *argv[])
|
||||
|
||||
const RenderData::Color clearColor(18, 18, 24, 255);
|
||||
const RenderData::Color cubeColor(240, 240, 240, 255);
|
||||
const RenderData::Color fpsColor(0, 255, 80, 255);
|
||||
const RenderData::Color fpsBg(0, 0, 0, 200);
|
||||
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
|
||||
|
||||
bool isRuning = true;
|
||||
while (isRuning)
|
||||
int32_t fps = 0;
|
||||
int32_t frame_count = 0;
|
||||
uint32_t last_fps_time = 0;
|
||||
char fps_text[32];
|
||||
|
||||
bool isRunning = true;
|
||||
uint32_t animation_time_ms = 0;
|
||||
while (isRunning)
|
||||
{
|
||||
display->poll_events(isRuning);
|
||||
timer.begin_frame(time_source.get_time_ms());
|
||||
const uint32_t fixed_delta_ms = timer.fixed_delta_ms();
|
||||
animation_time_ms += fixed_delta_ms;
|
||||
|
||||
frameBuffer->clear(clearColor);
|
||||
depthBuffer->clear();
|
||||
display->poll_events(isRunning);
|
||||
|
||||
const float timeSeconds = static_cast<float>(display->get_time_ms()) * 0.001f;
|
||||
ctx.clear(clearColor);
|
||||
|
||||
const float animation_time = static_cast<float>(animation_time_ms) / 1000.0f;
|
||||
const Math::Matrix4x4 model =
|
||||
Math::MathUtil::get_rotation_matrix_y(timeSeconds) *
|
||||
Math::MathUtil::get_rotation_matrix_x(timeSeconds * 0.6f);
|
||||
Math::MathUtil::get_rotation_matrix_y(animation_time) *
|
||||
Math::MathUtil::get_rotation_matrix_x(static_cast<float>(animation_time_ms * 6u) / 10000.0f);
|
||||
const Math::Matrix4x4 view = camera.get_view_matrix();
|
||||
const Math::Matrix4x4 modelView = view * model;
|
||||
const Math::Matrix4x4 projection = camera.get_perspective_projection_matrix(aspectRatio);
|
||||
@@ -361,10 +487,6 @@ int main(int argc, char *argv[])
|
||||
continue;
|
||||
}
|
||||
|
||||
const Math::Vector3 &viewV0 = viewSpaceVertices[cubeTriangle.vertices[0]];
|
||||
const Math::Vector3 &viewV1 = viewSpaceVertices[cubeTriangle.vertices[1]];
|
||||
const Math::Vector3 &viewV2 = viewSpaceVertices[cubeTriangle.vertices[2]];
|
||||
|
||||
drawTriangles[drawCommandCount++] =
|
||||
RenderData::Triangle(
|
||||
Scene::Vertex(v0.screen),
|
||||
@@ -374,7 +496,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
for (size_t i = 0; i < drawCommandCount; ++i)
|
||||
{
|
||||
triangleRasterizer.DrawTriangle2D(drawTriangles[i], cubeColor);
|
||||
ctx.draw_triangle(drawTriangles[i], cubeColor);
|
||||
}
|
||||
|
||||
for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex)
|
||||
@@ -396,19 +518,36 @@ int main(int argc, char *argv[])
|
||||
continue;
|
||||
}
|
||||
|
||||
rasterizer.DrawLine(
|
||||
ctx.draw_line(
|
||||
Math::Vector2(start.screen.x, start.screen.y).to_vector2Int(),
|
||||
Math::Vector2(end.screen.x, end.screen.y).to_vector2Int(),
|
||||
clearColor);
|
||||
}
|
||||
}
|
||||
|
||||
display->present(frameBuffer);
|
||||
// sprite 测试
|
||||
ctx.draw_sprite(10, 10, sprite_img);
|
||||
ctx.draw_sprite_region_ex(30, 10, sprite_region, 2, false, false);
|
||||
ctx.draw_sprite_region_ex(10, 30, sprite_region, 3, true, false);
|
||||
ctx.draw_tilemap(testTilemap, 650, 500, 96, 48, static_cast<int32_t>(animation_time_ms / 20u) % 32, 0);
|
||||
|
||||
// FPS 计数
|
||||
++frame_count;
|
||||
const uint32_t now = time_source.get_time_ms();
|
||||
if (now - last_fps_time >= 1000)
|
||||
{
|
||||
fps = frame_count;
|
||||
frame_count = 0;
|
||||
last_fps_time = now;
|
||||
}
|
||||
std::snprintf(fps_text, sizeof(fps_text), "FPS: %d", fps);
|
||||
ctx.draw_text(font, 4, 4, fpsColor, fpsBg, fps_text);
|
||||
|
||||
ctx.present(display);
|
||||
SleepRemainingFrameTime(timer, time_source);
|
||||
}
|
||||
|
||||
display->shutdown();
|
||||
delete display;
|
||||
delete frameBuffer;
|
||||
delete depthBuffer;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
#include "SpriteAssetLoader.h"
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
static const char SpriteMagic[4] = { 'S', 'P', 'R', 'T' };
|
||||
static const size_t SpriteHeaderSize = 20;
|
||||
static const uint32_t SpriteVersion = 1;
|
||||
static const uint32_t SpriteFormatRgba8888 = 1;
|
||||
static const uint64_t MaxSpritePixels = 8192ull * 8192ull;
|
||||
|
||||
static bool ReadExact(std::ifstream& file, char* data, size_t size)
|
||||
{
|
||||
file.read(data, static_cast<std::streamsize>(size));
|
||||
return static_cast<size_t>(file.gcount()) == size;
|
||||
}
|
||||
|
||||
static bool ReadU32LE(const std::vector<uint8_t>& bytes, size_t offset, uint32_t& value)
|
||||
{
|
||||
if (offset + 4 > bytes.size())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value =
|
||||
static_cast<uint32_t>(bytes[offset]) |
|
||||
(static_cast<uint32_t>(bytes[offset + 1]) << 8) |
|
||||
(static_cast<uint32_t>(bytes[offset + 2]) << 16) |
|
||||
(static_cast<uint32_t>(bytes[offset + 3]) << 24);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WriteU32LE(std::ofstream& file, uint32_t value)
|
||||
{
|
||||
const char bytes[4] = {
|
||||
static_cast<char>(value & 0xFF),
|
||||
static_cast<char>((value >> 8) & 0xFF),
|
||||
static_cast<char>((value >> 16) & 0xFF),
|
||||
static_cast<char>((value >> 24) & 0xFF)
|
||||
};
|
||||
file.write(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
static bool CheckPixelCount(uint32_t width, uint32_t height, size_t& pixelCount)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint64_t total = static_cast<uint64_t>(width) * static_cast<uint64_t>(height);
|
||||
if (total > MaxSpritePixels ||
|
||||
total > static_cast<uint64_t>(std::numeric_limits<size_t>::max() / sizeof(uint32_t)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
pixelCount = static_cast<size_t>(total);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ReadPixels(std::ifstream& file, std::vector<uint32_t>& pixels)
|
||||
{
|
||||
std::vector<uint8_t> bytes(pixels.size() * sizeof(uint32_t), 0);
|
||||
if (!ReadExact(file, reinterpret_cast<char*>(bytes.data()), bytes.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < pixels.size(); ++i)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
if (!ReadU32LE(bytes, i * sizeof(uint32_t), value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
pixels[i] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void WritePixels(std::ofstream& file, const RenderData::Image& image)
|
||||
{
|
||||
for (size_t i = 0; i < image.total_pixels(); ++i)
|
||||
{
|
||||
WriteU32LE(file, image.data()[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Asset
|
||||
{
|
||||
bool SpriteAssetLoader::Load(const std::string& path, RenderData::Image& image)
|
||||
{
|
||||
std::ifstream file(path.c_str(), std::ios::binary);
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> header(SpriteHeaderSize, 0);
|
||||
if (!ReadExact(file, reinterpret_cast<char*>(header.data()), header.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header[0] != SpriteMagic[0] ||
|
||||
header[1] != SpriteMagic[1] ||
|
||||
header[2] != SpriteMagic[2] ||
|
||||
header[3] != SpriteMagic[3])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t version = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t format = 0;
|
||||
if (!ReadU32LE(header, 4, version) ||
|
||||
!ReadU32LE(header, 8, width) ||
|
||||
!ReadU32LE(header, 12, height) ||
|
||||
!ReadU32LE(header, 16, format))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t pixelCount = 0;
|
||||
if (version != SpriteVersion ||
|
||||
format != SpriteFormatRgba8888 ||
|
||||
!CheckPixelCount(width, height, pixelCount))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> pixels(pixelCount, 0);
|
||||
if (!ReadPixels(file, pixels))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
char trailingByte = 0;
|
||||
file.read(&trailingByte, 1);
|
||||
if (file.gcount() != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
image = RenderData::Image(
|
||||
static_cast<int32_t>(width),
|
||||
static_cast<int32_t>(height),
|
||||
pixels);
|
||||
return image.is_valid();
|
||||
}
|
||||
|
||||
bool SpriteAssetLoader::Save(const std::string& path, const RenderData::Image& image)
|
||||
{
|
||||
if (!image.is_valid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path.c_str(), std::ios::binary);
|
||||
if (!file.good())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(SpriteMagic, sizeof(SpriteMagic));
|
||||
WriteU32LE(file, SpriteVersion);
|
||||
WriteU32LE(file, static_cast<uint32_t>(image.get_width()));
|
||||
WriteU32LE(file, static_cast<uint32_t>(image.get_height()));
|
||||
WriteU32LE(file, SpriteFormatRgba8888);
|
||||
WritePixels(file, image);
|
||||
return file.good();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include "Image.h"
|
||||
|
||||
namespace Asset
|
||||
{
|
||||
class SpriteAssetLoader
|
||||
{
|
||||
public:
|
||||
static const char* GetFileExtension() { return ".sprite"; }
|
||||
static bool Load(const std::string& path, RenderData::Image& image);
|
||||
static bool Save(const std::string& path, const RenderData::Image& image);
|
||||
};
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
# CPU 软件渲染器项目约定
|
||||
|
||||
本文档用于记录当前项目已经采用的坐标系、矩阵、相机和屏幕空间约定,避免后续开发时在符号、方向和乘法顺序上产生混乱。
|
||||
|
||||
## 1. 通用约定
|
||||
|
||||
- 项目使用右手坐标系。
|
||||
- `Vector3::cross(a, b)` 遵循右手定则。
|
||||
- 除非特别说明,向量按列向量理解。
|
||||
- 变换写法按 `M * v` 解释。
|
||||
|
||||
## 2. 世界空间与局部空间
|
||||
|
||||
世界空间与物体局部空间目前使用同一套方向命名:
|
||||
|
||||
- `+X`:右
|
||||
- `+Y`:上
|
||||
- `+Z`:前
|
||||
|
||||
`Scene::Transform` 也遵循这套约定:
|
||||
|
||||
- `get_right()`:旋转后的局部右方向
|
||||
- `get_up()`:旋转后的局部上方向
|
||||
- `get_forward()`:旋转后的局部前方向
|
||||
- `get_left()`、`get_down()`、`get_back()`:分别为对应反方向
|
||||
|
||||
也就是说,`Transform` 中的 `forward` 明确定义为 `+Z`。
|
||||
|
||||
## 3. 旋转约定
|
||||
|
||||
- 欧拉角使用弧度制。
|
||||
- `rotation.x`:绕 `X` 轴旋转
|
||||
- `rotation.y`:绕 `Y` 轴旋转
|
||||
- `rotation.z`:绕 `Z` 轴旋转
|
||||
- 组合旋转顺序为 `Rz * Ry * Rx`
|
||||
|
||||
当前项目里,方向向量的旋转方式是先构造旋转矩阵,再去变换基础方向轴。
|
||||
|
||||
## 4. 矩阵约定
|
||||
|
||||
`Math::Matrix4x4` 当前采用以下规则:
|
||||
|
||||
- 语义上按列向量使用,写法为 `M * v`
|
||||
- 元素访问方式为 `matrix[row][col]`
|
||||
- `data()` 暴露的是连续的 row-major 内存
|
||||
- 平移分量存放在最后一列
|
||||
|
||||
因此,常见的组合变换阅读顺序是从右往左:
|
||||
|
||||
- `worldPosition = Translation * Rotation * Scale * localPosition`
|
||||
|
||||
## 5. 相机与视图空间
|
||||
|
||||
相机目前由 `Scene::Camera::transform` 驱动。
|
||||
|
||||
相机自身局部方向定义为:
|
||||
|
||||
- 相机右方向:`transform.get_right()`
|
||||
- 相机上方向:`transform.get_up()`
|
||||
- 相机前方向:`transform.get_forward()`
|
||||
|
||||
但进入视图空间后,当前项目采用的是常见的相机空间约定:
|
||||
|
||||
- 位于相机前方的点,其 view-space `z` 为负值
|
||||
- 视图矩阵第三行存的是相机 backward,而不是 forward
|
||||
|
||||
这和当前透视投影矩阵实现是一致的,因为那里对应的是 `clip.w = -viewZ` 这套约定。
|
||||
|
||||
## 6. 三角形绕序与正面约定
|
||||
|
||||
当前项目将三角形正面统一约定为顺时针 `CW` 绕序。
|
||||
|
||||
这里的“顺时针”按当前渲染流程解释为:
|
||||
|
||||
- 三角形经过 view / projection / viewport 变换后
|
||||
- 从屏幕上观察其顶点顺序时,正面三角形按顺时针排列
|
||||
|
||||
与这套约定配套的实现规则是:
|
||||
|
||||
- 背面剔除当前在 view space 中完成
|
||||
- 法线方向使用 `faceNormal = (v1 - v0).cross(v2 - v0)` 计算
|
||||
- 当前 demo 中,`faceNormal.dot(faceCenter) > 0` 被视为正面
|
||||
|
||||
这意味着:
|
||||
|
||||
- 所有手写或导入的三角形索引都必须保持一致绕序
|
||||
- 如果未来改成逆时针 `CCW` 为正面,那么剔除判定符号也必须同步调整
|
||||
- `main.cpp` 里的 `cubeTriangles` 和 `cubeFaces` 当前应继续保持与这套约定一致,不要单独翻转其中一部分
|
||||
|
||||
## 7. 投影与 NDC
|
||||
|
||||
当前透视投影相关约定如下:
|
||||
|
||||
- 规范化设备坐标(NDC)可见范围为 `[-1, 1]`
|
||||
- `x`、`y`、`z` 都会在映射到 viewport 之前做范围检查
|
||||
- 当前测试代码里,如果点超出 NDC,可能会直接判为不可见,而不是继续做线段裁剪
|
||||
|
||||
这意味着:
|
||||
|
||||
- 当前 demo 还没有实现完整的视锥裁剪
|
||||
- 如果一条线段只有一部分还在屏幕内,但端点已经越出 NDC,整条线仍可能被直接丢弃
|
||||
|
||||
## 8. 屏幕与像素坐标
|
||||
|
||||
屏幕/像素坐标使用左上角为原点的约定:
|
||||
|
||||
- 原点在左上角
|
||||
- `x` 向右增大
|
||||
- `y` 向下增大
|
||||
|
||||
`Core::FrameBuffer` 当前行为如下:
|
||||
|
||||
- 有效像素范围:`x in [0, width)`
|
||||
- 有效像素范围:`y in [0, height)`
|
||||
- 越界写入会被直接忽略
|
||||
- 像素缓冲按 row-major 排列
|
||||
- 内存中的第一行对应 `y = 0`
|
||||
|
||||
`Camera::get_viewport_matrix()` 里也对 `Y` 做了翻转,因此 NDC 的“向上”为正,最终会映射成屏幕坐标“向下”为正。
|
||||
|
||||
## 9. 深度缓冲约定
|
||||
|
||||
当前项目已经接入 `Core::DepthBuffer`,并采用以下规则:
|
||||
|
||||
- `DepthBuffer` 存储类型为 `float`
|
||||
- 每帧开始时必须调用 `depthBuffer->clear()`,默认清为 `INFINITY`
|
||||
- 当前约定为“深度值越小,离相机越近”
|
||||
- 深度测试通过后,必须同时更新 `DepthBuffer` 和 `FrameBuffer`
|
||||
- `DepthBuffer` 只负责存储和读取深度,不负责决定颜色写入逻辑;是否写颜色由光栅化阶段决定
|
||||
|
||||
当前三角形光栅化里的深度流程为:
|
||||
|
||||
- 在屏幕空间遍历三角形包围盒
|
||||
- 以像素中心 `x + 0.5, y + 0.5` 作为采样点
|
||||
- 用屏幕空间 `x/y` 计算重心坐标
|
||||
- 用同一组重心坐标判断点是否在三角形内,并插值顶点 `z`
|
||||
- 若新深度更近,则写入 `DepthBuffer` 和 `FrameBuffer`
|
||||
|
||||
也就是说:
|
||||
|
||||
- 重心坐标的计算是二维问题,只使用屏幕空间 `x/y`
|
||||
- 顶点 `z` 的插值使用这组重心权重完成
|
||||
- 当前实现是屏幕空间线性插值,后续如果引入纹理、法线或更严格的属性插值,需要进一步考虑透视校正插值
|
||||
|
||||
## 10. Demo 中的可见性规则
|
||||
|
||||
`main.cpp` 里的旋转立方体示例,目前采用以下可见性与遮挡规则:
|
||||
|
||||
- 三角形是否参与光栅化,仍由投影合法性检查和背面剔除决定
|
||||
- 三角形之间的遮挡,不再依赖按平均深度排序,而是由 `DepthBuffer` 逐像素决定
|
||||
- 当前 demo 仍保留按面筛选后的轮廓线绘制,用于显示黑色边框
|
||||
|
||||
这意味着:
|
||||
|
||||
- `DepthBuffer` 不能替代投影合法性检查或背面剔除
|
||||
- `DepthBuffer` 负责的是像素级遮挡,而不是顶点级或三角形级是否进入渲染流程
|
||||
|
||||
后续如果项目要加入更严格的裁剪、剔除、透视校正插值或隐藏线规则,应当以代码实现为准,并同步更新本文档。
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class Timer
|
||||
{
|
||||
public:
|
||||
static const uint32_t DefaultFps = 30;
|
||||
|
||||
explicit Timer(uint32_t target_fps = DefaultFps)
|
||||
: target_fps_(normalize_fps(target_fps)),
|
||||
tick_remainder_(0),
|
||||
frame_start_ms_(0),
|
||||
fixed_delta_ms_(0)
|
||||
{
|
||||
}
|
||||
|
||||
void begin_frame(uint32_t now_ms)
|
||||
{
|
||||
frame_start_ms_ = now_ms;
|
||||
fixed_delta_ms_ = next_tick_ms();
|
||||
}
|
||||
|
||||
uint32_t target_fps() const
|
||||
{
|
||||
return target_fps_;
|
||||
}
|
||||
|
||||
uint32_t fixed_delta_ms() const
|
||||
{
|
||||
return fixed_delta_ms_;
|
||||
}
|
||||
|
||||
uint32_t frame_start_ms() const
|
||||
{
|
||||
return frame_start_ms_;
|
||||
}
|
||||
|
||||
uint32_t remaining_frame_ms(uint32_t now_ms) const
|
||||
{
|
||||
const uint32_t elapsed_ms = now_ms - frame_start_ms_;
|
||||
return elapsed_ms < fixed_delta_ms_ ? fixed_delta_ms_ - elapsed_ms : 0u;
|
||||
}
|
||||
|
||||
static bool is_supported_fps(uint32_t fps)
|
||||
{
|
||||
return fps == 30u || fps == 45u || fps == 60u;
|
||||
}
|
||||
|
||||
static uint32_t normalize_fps(uint32_t fps)
|
||||
{
|
||||
return is_supported_fps(fps) ? fps : DefaultFps;
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t next_tick_ms()
|
||||
{
|
||||
tick_remainder_ += 1000u;
|
||||
const uint32_t tick_ms = tick_remainder_ / target_fps_;
|
||||
tick_remainder_ %= target_fps_;
|
||||
return tick_ms;
|
||||
}
|
||||
|
||||
uint32_t target_fps_;
|
||||
uint32_t tick_remainder_;
|
||||
uint32_t frame_start_ms_;
|
||||
uint32_t fixed_delta_ms_;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
#include "DrawContext.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include "DepthBuffer.h"
|
||||
#include "Rasterizer.h"
|
||||
#include "TriangleRasterizer.h"
|
||||
#include "Display.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace Gfx
|
||||
{
|
||||
DrawContext::DrawContext(int32_t width, int32_t height)
|
||||
{
|
||||
frameBuffer = new Core::FrameBuffer(width, height);
|
||||
depthBuffer = new Core::DepthBuffer(width, height);
|
||||
rasterizer = new Rasterizer::Rasterizer(frameBuffer, depthBuffer);
|
||||
triangleRasterizer = new Rasterizer::TriangleRasterizer(frameBuffer, depthBuffer);
|
||||
}
|
||||
|
||||
DrawContext::~DrawContext()
|
||||
{
|
||||
delete triangleRasterizer;
|
||||
delete rasterizer;
|
||||
delete depthBuffer;
|
||||
delete frameBuffer;
|
||||
}
|
||||
|
||||
int32_t DrawContext::get_width() const
|
||||
{
|
||||
return frameBuffer->get_width();
|
||||
}
|
||||
|
||||
int32_t DrawContext::get_height() const
|
||||
{
|
||||
return frameBuffer->get_height();
|
||||
}
|
||||
|
||||
void DrawContext::clear(const RenderData::Color& color)
|
||||
{
|
||||
frameBuffer->clear(color);
|
||||
depthBuffer->clear();
|
||||
}
|
||||
|
||||
void DrawContext::clear_depth()
|
||||
{
|
||||
depthBuffer->clear();
|
||||
}
|
||||
|
||||
void DrawContext::draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color)
|
||||
{
|
||||
rasterizer->DrawLine(from, to, color);
|
||||
}
|
||||
|
||||
void DrawContext::draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color)
|
||||
{
|
||||
triangleRasterizer->DrawTriangle2D(triangle, color);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img)
|
||||
{
|
||||
draw_sprite_region(dst_x, dst_y, img, 0, 0, img.width, img.height);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
|
||||
{
|
||||
draw_sprite_region(dst_x, dst_y, region);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h)
|
||||
{
|
||||
draw_sprite_ex(dst_x, dst_y, img, src_x, src_y, src_w, src_h, 1, false, false);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region)
|
||||
{
|
||||
draw_sprite_region_ex(dst_x, dst_y, region, 1, false, false);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
|
||||
int32_t scale, bool flip_h, bool flip_v)
|
||||
{
|
||||
if (!region.atlas) return;
|
||||
|
||||
draw_sprite_ex(
|
||||
dst_x,
|
||||
dst_y,
|
||||
*region.atlas,
|
||||
region.x,
|
||||
region.y,
|
||||
region.width,
|
||||
region.height,
|
||||
scale,
|
||||
flip_h,
|
||||
flip_v);
|
||||
}
|
||||
|
||||
void DrawContext::draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
||||
int32_t scale, bool flip_h, bool flip_v)
|
||||
{
|
||||
if (scale < 1 || !img.pixels || src_w <= 0 || src_h <= 0) return;
|
||||
if (src_x < 0 || src_y < 0 || src_x + src_w > img.width || src_y + src_h > img.height) return;
|
||||
|
||||
const int32_t img_w = img.width;
|
||||
const int32_t screen_w = frameBuffer->get_width();
|
||||
const int32_t screen_h = frameBuffer->get_height();
|
||||
const int32_t draw_w = src_w * scale;
|
||||
const int32_t draw_h = src_h * scale;
|
||||
|
||||
if (dst_x >= screen_w || dst_y >= screen_h || dst_x + draw_w <= 0 || dst_y + draw_h <= 0) return;
|
||||
|
||||
int32_t start_dx = 0;
|
||||
int32_t start_dy = 0;
|
||||
int32_t end_dx = draw_w;
|
||||
int32_t end_dy = draw_h;
|
||||
|
||||
if (dst_x < 0) start_dx = -dst_x;
|
||||
if (dst_y < 0) start_dy = -dst_y;
|
||||
if (dst_x + end_dx > screen_w) end_dx = screen_w - dst_x;
|
||||
if (dst_y + end_dy > screen_h) end_dy = screen_h - dst_y;
|
||||
|
||||
const uint32_t* src = img.pixels;
|
||||
const bool has_key = img.has_color_key;
|
||||
const uint32_t key = img.color_key;
|
||||
|
||||
if (scale == 1)
|
||||
{
|
||||
for (int32_t sy = start_dy; sy < end_dy; ++sy)
|
||||
{
|
||||
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
|
||||
const uint32_t* row = src + read_y * img_w;
|
||||
const int32_t dst_y_abs = dst_y + sy;
|
||||
|
||||
for (int32_t sx = start_dx; sx < end_dx; ++sx)
|
||||
{
|
||||
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
|
||||
const uint32_t pixel = row[read_x];
|
||||
|
||||
if (has_key && pixel == key) continue;
|
||||
|
||||
frameBuffer->set_pixel(dst_x + sx, dst_y_abs, pixel);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t dy_abs = start_dy; dy_abs < end_dy; ++dy_abs)
|
||||
{
|
||||
const int32_t sy = dy_abs / scale;
|
||||
const int32_t read_y = flip_v ? (src_y + src_h - 1 - sy) : (src_y + sy);
|
||||
const uint32_t* row = src + read_y * img_w;
|
||||
const int32_t dst_y_abs = dst_y + dy_abs;
|
||||
|
||||
for (int32_t dx_abs = start_dx; dx_abs < end_dx; ++dx_abs)
|
||||
{
|
||||
const int32_t sx = dx_abs / scale;
|
||||
const int32_t read_x = flip_h ? (src_x + src_w - 1 - sx) : (src_x + sx);
|
||||
const uint32_t pixel = row[read_x];
|
||||
|
||||
if (has_key && pixel == key) continue;
|
||||
|
||||
frameBuffer->set_pixel(dst_x + dx_abs, dst_y_abs, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||
int32_t screen_x, int32_t screen_y,
|
||||
int32_t camera_x, int32_t camera_y)
|
||||
{
|
||||
draw_tilemap(tilemap,
|
||||
screen_x,
|
||||
screen_y,
|
||||
frameBuffer->get_width() - screen_x,
|
||||
frameBuffer->get_height() - screen_y,
|
||||
camera_x,
|
||||
camera_y);
|
||||
}
|
||||
|
||||
void DrawContext::draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||
int32_t screen_x, int32_t screen_y,
|
||||
int32_t viewport_w, int32_t viewport_h,
|
||||
int32_t camera_x, int32_t camera_y)
|
||||
{
|
||||
if (!tilemap.tiles || !tilemap.atlas || !tilemap.atlas->pixels) return;
|
||||
if (tilemap.width <= 0 || tilemap.height <= 0) return;
|
||||
if (tilemap.tile_w <= 0 || tilemap.tile_h <= 0 || tilemap.atlas_columns <= 0) return;
|
||||
if (viewport_w <= 0 || viewport_h <= 0) return;
|
||||
|
||||
const int32_t viewport_left = screen_x;
|
||||
const int32_t viewport_top = screen_y;
|
||||
const int32_t viewport_right = screen_x + viewport_w;
|
||||
const int32_t viewport_bottom = screen_y + viewport_h;
|
||||
|
||||
int32_t start_tile_x = camera_x / tilemap.tile_w;
|
||||
int32_t start_tile_y = camera_y / tilemap.tile_h;
|
||||
int32_t offset_x = -(camera_x % tilemap.tile_w);
|
||||
int32_t offset_y = -(camera_y % tilemap.tile_h);
|
||||
|
||||
if (camera_x < 0 && camera_x % tilemap.tile_w != 0)
|
||||
{
|
||||
--start_tile_x;
|
||||
offset_x = -camera_x - (-start_tile_x * tilemap.tile_w);
|
||||
}
|
||||
if (camera_y < 0 && camera_y % tilemap.tile_h != 0)
|
||||
{
|
||||
--start_tile_y;
|
||||
offset_y = -camera_y - (-start_tile_y * tilemap.tile_h);
|
||||
}
|
||||
|
||||
const int32_t visible_cols = viewport_w / tilemap.tile_w + 2;
|
||||
const int32_t visible_rows = viewport_h / tilemap.tile_h + 2;
|
||||
|
||||
for (int32_t row = 0; row < visible_rows; ++row)
|
||||
{
|
||||
const int32_t map_y = start_tile_y + row;
|
||||
if (map_y < 0 || map_y >= tilemap.height) continue;
|
||||
|
||||
const int32_t dst_y = screen_y + offset_y + row * tilemap.tile_h;
|
||||
for (int32_t col = 0; col < visible_cols; ++col)
|
||||
{
|
||||
const int32_t map_x = start_tile_x + col;
|
||||
if (map_x < 0 || map_x >= tilemap.width) continue;
|
||||
|
||||
const uint16_t tile_id = tilemap.get_tile(map_x, map_y);
|
||||
if (tile_id == RenderData::Tilemap::EmptyTile) continue;
|
||||
|
||||
const int32_t src_x = (tile_id % tilemap.atlas_columns) * tilemap.tile_w;
|
||||
const int32_t src_y = (tile_id / tilemap.atlas_columns) * tilemap.tile_h;
|
||||
const int32_t dst_x = screen_x + offset_x + col * tilemap.tile_w;
|
||||
const int32_t tile_right = dst_x + tilemap.tile_w;
|
||||
const int32_t tile_bottom = dst_y + tilemap.tile_h;
|
||||
|
||||
int32_t clipped_left = dst_x;
|
||||
int32_t clipped_top = dst_y;
|
||||
int32_t clipped_right = tile_right;
|
||||
int32_t clipped_bottom = tile_bottom;
|
||||
|
||||
if (clipped_left < viewport_left) clipped_left = viewport_left;
|
||||
if (clipped_top < viewport_top) clipped_top = viewport_top;
|
||||
if (clipped_right > viewport_right) clipped_right = viewport_right;
|
||||
if (clipped_bottom > viewport_bottom) clipped_bottom = viewport_bottom;
|
||||
|
||||
if (clipped_left >= clipped_right || clipped_top >= clipped_bottom) continue;
|
||||
|
||||
const int32_t clipped_src_x = src_x + (clipped_left - dst_x);
|
||||
const int32_t clipped_src_y = src_y + (clipped_top - dst_y);
|
||||
const int32_t clipped_w = clipped_right - clipped_left;
|
||||
const int32_t clipped_h = clipped_bottom - clipped_top;
|
||||
|
||||
draw_sprite_ex(clipped_left, clipped_top, *tilemap.atlas,
|
||||
clipped_src_x, clipped_src_y, clipped_w, clipped_h,
|
||||
1, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color)
|
||||
{
|
||||
const uint32_t rgba = color.to_rgba();
|
||||
for (int32_t row = y; row < y + h; ++row)
|
||||
{
|
||||
for (int32_t col = x; col < x + w; ++col)
|
||||
{
|
||||
frameBuffer->set_pixel(col, row, rgba);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const char* text)
|
||||
{
|
||||
if (!font.atlas.pixels || !text) return;
|
||||
|
||||
const uint32_t rgba = color.to_rgba();
|
||||
const int32_t cw = font.char_w;
|
||||
const int32_t ch = font.char_h;
|
||||
const int32_t atlas_w = font.atlas.width;
|
||||
const uint32_t* src = font.atlas.pixels;
|
||||
|
||||
int32_t cursor_x = x;
|
||||
for (const char* p = text; *p; ++p)
|
||||
{
|
||||
const int32_t index = static_cast<int32_t>(*p) - font.first_char;
|
||||
if (index < 0) { cursor_x += cw; continue; }
|
||||
|
||||
const int32_t col = index % font.columns;
|
||||
const int32_t row = index / font.columns;
|
||||
const int32_t src_x = col * cw;
|
||||
const int32_t src_y = row * ch;
|
||||
|
||||
for (int32_t sy = 0; sy < ch; ++sy)
|
||||
{
|
||||
const uint32_t* atlas_row = src + (src_y + sy) * atlas_w;
|
||||
const int32_t dst_y_abs = y + sy;
|
||||
for (int32_t sx = 0; sx < cw; ++sx)
|
||||
{
|
||||
const uint32_t pixel = atlas_row[src_x + sx];
|
||||
if ((pixel & 0xFF) == 0) continue;
|
||||
frameBuffer->set_pixel(cursor_x + sx, dst_y_abs, rgba);
|
||||
}
|
||||
}
|
||||
cursor_x += cw;
|
||||
}
|
||||
}
|
||||
|
||||
void DrawContext::draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const RenderData::Color& bg_color, const char* text)
|
||||
{
|
||||
if (!text) return;
|
||||
|
||||
int32_t len = 0;
|
||||
for (const char* p = text; *p; ++p) ++len;
|
||||
|
||||
fill_rect(x, y, len * font.char_w, font.char_h, bg_color);
|
||||
draw_text(font, x, y, color, text);
|
||||
}
|
||||
|
||||
void DrawContext::present(Platform::IDisplay* display)
|
||||
{
|
||||
display->present(frameBuffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
#include "Color.h"
|
||||
#include "Vector2.h"
|
||||
#include "Triangle.h"
|
||||
#include "Image.h"
|
||||
#include "SpriteRegion.h"
|
||||
#include "Tilemap.h"
|
||||
#include "BitmapFont.h"
|
||||
#include <cstdint>
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class FrameBuffer;
|
||||
class DepthBuffer;
|
||||
}
|
||||
|
||||
namespace Rasterizer
|
||||
{
|
||||
class Rasterizer;
|
||||
class TriangleRasterizer;
|
||||
}
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
class IDisplay;
|
||||
}
|
||||
|
||||
namespace Gfx
|
||||
{
|
||||
class DrawContext
|
||||
{
|
||||
private:
|
||||
Core::FrameBuffer* frameBuffer;
|
||||
Core::DepthBuffer* depthBuffer;
|
||||
Rasterizer::Rasterizer* rasterizer;
|
||||
Rasterizer::TriangleRasterizer* triangleRasterizer;
|
||||
|
||||
public:
|
||||
DrawContext(int32_t width, int32_t height);
|
||||
~DrawContext();
|
||||
|
||||
DrawContext(const DrawContext&) = delete;
|
||||
DrawContext& operator=(const DrawContext&) = delete;
|
||||
|
||||
int32_t get_width() const;
|
||||
int32_t get_height() const;
|
||||
|
||||
void clear(const RenderData::Color& color);
|
||||
void clear_depth();
|
||||
|
||||
void draw_line(const Math::Vector2Int& from, const Math::Vector2Int& to, const RenderData::Color& color);
|
||||
void draw_triangle(const RenderData::Triangle& triangle, const RenderData::Color& color);
|
||||
|
||||
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::Image& img);
|
||||
void draw_sprite(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region);
|
||||
void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h);
|
||||
void draw_sprite_region(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region);
|
||||
void draw_sprite_region_ex(int32_t dst_x, int32_t dst_y, const RenderData::SpriteRegion& region,
|
||||
int32_t scale, bool flip_h, bool flip_v);
|
||||
void draw_sprite_ex(int32_t dst_x, int32_t dst_y, const RenderData::Image& img,
|
||||
int32_t src_x, int32_t src_y, int32_t src_w, int32_t src_h,
|
||||
int32_t scale, bool flip_h, bool flip_v);
|
||||
void draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||
int32_t screen_x, int32_t screen_y,
|
||||
int32_t camera_x, int32_t camera_y);
|
||||
void draw_tilemap(const RenderData::Tilemap& tilemap,
|
||||
int32_t screen_x, int32_t screen_y,
|
||||
int32_t viewport_w, int32_t viewport_h,
|
||||
int32_t camera_x, int32_t camera_y);
|
||||
|
||||
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, const RenderData::Color& color);
|
||||
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const char* text);
|
||||
void draw_text(const RenderData::BitmapFont& font, int32_t x, int32_t y,
|
||||
const RenderData::Color& color, const RenderData::Color& bg_color, const char* text);
|
||||
|
||||
void present(Platform::IDisplay* display);
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,6 @@ namespace Platform
|
||||
virtual bool init(int width, int height) = 0;
|
||||
virtual void present(const Core::FrameBuffer* framebuffer) = 0;
|
||||
virtual void poll_events(bool& should_quit) = 0;
|
||||
virtual uint32_t get_time_ms() const = 0;
|
||||
virtual void shutdown() = 0;
|
||||
};
|
||||
}
|
||||
@@ -119,13 +119,6 @@ namespace Platform
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t FBDisplay::get_time_ms() const
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<uint32_t>(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
|
||||
}
|
||||
|
||||
void FBDisplay::shutdown()
|
||||
{
|
||||
if (fb_mem != nullptr)
|
||||
@@ -21,7 +21,6 @@ namespace Platform
|
||||
bool init(int w, int h) override;
|
||||
void present(const Core::FrameBuffer* framebuffer) override;
|
||||
void poll_events(bool& should_quit) override;
|
||||
uint32_t get_time_ms() const override;
|
||||
void shutdown() override;
|
||||
};
|
||||
}
|
||||
@@ -76,11 +76,6 @@ namespace Platform
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t SDLDisplay::get_time_ms() const
|
||||
{
|
||||
return SDL_GetTicks();
|
||||
}
|
||||
|
||||
void SDLDisplay::shutdown()
|
||||
{
|
||||
if (texture != nullptr)
|
||||
@@ -17,7 +17,6 @@ namespace Platform
|
||||
bool init(int w, int h) override;
|
||||
void present(const Core::FrameBuffer* framebuffer) override;
|
||||
void poll_events(bool& should_quit) override;
|
||||
uint32_t get_time_ms() const override;
|
||||
void shutdown() override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <chrono>
|
||||
#else
|
||||
#include <ctime>
|
||||
#endif
|
||||
|
||||
namespace Platform
|
||||
{
|
||||
class ITimeSource
|
||||
{
|
||||
public:
|
||||
virtual ~ITimeSource() {}
|
||||
virtual uint32_t get_time_ms() const = 0;
|
||||
};
|
||||
|
||||
class SteadyTimeSource : public ITimeSource
|
||||
{
|
||||
public:
|
||||
SteadyTimeSource()
|
||||
:
|
||||
#ifdef _WIN32
|
||||
start_(std::chrono::steady_clock::now())
|
||||
#else
|
||||
start_ms_(read_monotonic_ms())
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t get_time_ms() const override
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
|
||||
const std::chrono::milliseconds elapsed =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now - start_);
|
||||
return static_cast<uint32_t>(elapsed.count());
|
||||
#else
|
||||
return read_monotonic_ms() - start_ms_;
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
std::chrono::steady_clock::time_point start_;
|
||||
#else
|
||||
static uint32_t read_monotonic_ms()
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return static_cast<uint32_t>(ts.tv_sec * 1000u + ts.tv_nsec / 1000000u);
|
||||
}
|
||||
|
||||
uint32_t start_ms_;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Image.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct BitmapFont
|
||||
{
|
||||
Image atlas;
|
||||
int32_t char_w;
|
||||
int32_t char_h;
|
||||
int32_t columns;
|
||||
int32_t first_char;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Color.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct Image
|
||||
{
|
||||
const uint32_t* pixels;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
uint32_t color_key;
|
||||
bool has_color_key;
|
||||
|
||||
Image() : pixels(nullptr), width(0), height(0), color_key(0), has_color_key(false) {}
|
||||
|
||||
Image(const uint32_t* pixels, int32_t width, int32_t height)
|
||||
: pixels(pixels), width(width), height(height), color_key(0), has_color_key(false) {}
|
||||
|
||||
Image(const uint32_t* pixels, int32_t width, int32_t height, uint32_t color_key)
|
||||
: pixels(pixels), width(width), height(height), color_key(color_key), has_color_key(true) {}
|
||||
|
||||
uint32_t get_pixel(int32_t x, int32_t y) const
|
||||
{
|
||||
return pixels[y * width + x];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Image.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct SpriteRegion
|
||||
{
|
||||
const Image* atlas;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
SpriteRegion()
|
||||
: atlas(nullptr), x(0), y(0), width(0), height(0) {}
|
||||
|
||||
SpriteRegion(const Image* atlas, int32_t x, int32_t y, int32_t width, int32_t height)
|
||||
: atlas(atlas), x(x), y(y), width(width), height(height) {}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Image.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct Tilemap
|
||||
{
|
||||
static const uint16_t EmptyTile = 0xFFFF;
|
||||
|
||||
const uint16_t* tiles;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
const Image* atlas;
|
||||
int32_t tile_w;
|
||||
int32_t tile_h;
|
||||
int32_t atlas_columns;
|
||||
|
||||
Tilemap()
|
||||
: tiles(nullptr),
|
||||
width(0),
|
||||
height(0),
|
||||
atlas(nullptr),
|
||||
tile_w(0),
|
||||
tile_h(0),
|
||||
atlas_columns(0)
|
||||
{
|
||||
}
|
||||
|
||||
Tilemap(const uint16_t* tiles, int32_t width, int32_t height,
|
||||
const Image* atlas, int32_t tile_w, int32_t tile_h, int32_t atlas_columns)
|
||||
: tiles(tiles),
|
||||
width(width),
|
||||
height(height),
|
||||
atlas(atlas),
|
||||
tile_w(tile_w),
|
||||
tile_h(tile_h),
|
||||
atlas_columns(atlas_columns)
|
||||
{
|
||||
}
|
||||
|
||||
uint16_t get_tile(int32_t x, int32_t y) const
|
||||
{
|
||||
return tiles[y * width + x];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "SpriteRasterizer.h"
|
||||
#include "../Core/FrameBuffer.h"
|
||||
#include "../RenderData/Image.h"
|
||||
#include "../RenderData/Sprite.h"
|
||||
|
||||
namespace Rasterizer
|
||||
{
|
||||
void SpriteRasterizer::DrawImage(const RenderData::Image& image, int32_t x, int32_t y)
|
||||
{
|
||||
DrawSprite(RenderData::Sprite(&image), x, y);
|
||||
}
|
||||
|
||||
void SpriteRasterizer::DrawSprite(const RenderData::Sprite& sprite, int32_t x, int32_t y)
|
||||
{
|
||||
if (frameBuffer == nullptr || !sprite.is_valid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int32_t minX = x < 0 ? -x : 0;
|
||||
const int32_t minY = y < 0 ? -y : 0;
|
||||
const int32_t maxX = x + sprite.width > frameBuffer->get_width() ? frameBuffer->get_width() - x : sprite.width;
|
||||
const int32_t maxY = y + sprite.height > frameBuffer->get_height() ? frameBuffer->get_height() - y : sprite.height;
|
||||
|
||||
if (minX >= maxX || minY >= maxY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t sy = minY; sy < maxY; ++sy)
|
||||
{
|
||||
for (int32_t sx = minX; sx < maxX; ++sx)
|
||||
{
|
||||
const uint32_t color = sprite.get_pixel_fast(sx, sy);
|
||||
const uint8_t alpha = static_cast<uint8_t>(color & 0xFF);
|
||||
if (alpha == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
frameBuffer->set_pixel(x + sx, y + sy, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class FrameBuffer;
|
||||
}
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
class Image;
|
||||
struct Sprite;
|
||||
}
|
||||
|
||||
namespace Rasterizer
|
||||
{
|
||||
class SpriteRasterizer
|
||||
{
|
||||
private:
|
||||
Core::FrameBuffer* frameBuffer;
|
||||
|
||||
public:
|
||||
explicit SpriteRasterizer(Core::FrameBuffer* frameBuffer) : frameBuffer(frameBuffer) {}
|
||||
|
||||
void DrawImage(const RenderData::Image& image, int32_t x, int32_t y);
|
||||
void DrawSprite(const RenderData::Sprite& sprite, int32_t x, int32_t y);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
class Image
|
||||
{
|
||||
private:
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
std::vector<uint32_t> pixels;
|
||||
|
||||
static bool calculate_pixel_count(int32_t width, int32_t height, size_t& count)
|
||||
{
|
||||
if (width <= 0 || height <= 0)
|
||||
{
|
||||
count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t safeWidth = static_cast<size_t>(width);
|
||||
const size_t safeHeight = static_cast<size_t>(height);
|
||||
if (safeWidth > std::numeric_limits<size_t>::max() / safeHeight)
|
||||
{
|
||||
count = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
count = safeWidth * safeHeight;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
Image() : width(0), height(0) {}
|
||||
|
||||
Image(int32_t width, int32_t height) : width(0), height(0)
|
||||
{
|
||||
size_t pixelCount = 0;
|
||||
if (calculate_pixel_count(width, height, pixelCount))
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
pixels.assign(pixelCount, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Image(int32_t width, int32_t height, const std::vector<uint32_t>& pixels) : width(0), height(0)
|
||||
{
|
||||
size_t pixelCount = 0;
|
||||
if (calculate_pixel_count(width, height, pixelCount) && pixels.size() == pixelCount)
|
||||
{
|
||||
this->width = width;
|
||||
this->height = height;
|
||||
this->pixels = pixels;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t get_width() const { return width; }
|
||||
|
||||
int32_t get_height() const { return height; }
|
||||
|
||||
size_t total_pixels() const { return pixels.size(); }
|
||||
|
||||
const uint32_t* data() const { return pixels.data(); }
|
||||
|
||||
uint32_t* data() { return pixels.data(); }
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
size_t pixelCount = 0;
|
||||
return calculate_pixel_count(width, height, pixelCount) && pixels.size() == pixelCount;
|
||||
}
|
||||
|
||||
uint32_t get_pixel(int32_t x, int32_t y) const
|
||||
{
|
||||
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t index = static_cast<size_t>(y) * width + x;
|
||||
return pixels[index];
|
||||
}
|
||||
|
||||
uint32_t get_pixel_fast(int32_t x, int32_t y) const
|
||||
{
|
||||
size_t index = static_cast<size_t>(y) * width + x;
|
||||
return pixels[index];
|
||||
}
|
||||
|
||||
void set_pixel(int32_t x, int32_t y, uint32_t color)
|
||||
{
|
||||
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
size_t index = static_cast<size_t>(y) * width + x;
|
||||
pixels.at(index) = color;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include "Image.h"
|
||||
|
||||
namespace RenderData
|
||||
{
|
||||
struct Sprite
|
||||
{
|
||||
const Image* image;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
|
||||
Sprite() : image(nullptr), x(0), y(0), width(0), height(0) {}
|
||||
|
||||
Sprite(const Image* image) :
|
||||
image(image),
|
||||
x(0),
|
||||
y(0),
|
||||
width(image ? image->get_width() : 0),
|
||||
height(image ? image->get_height() : 0)
|
||||
{}
|
||||
|
||||
Sprite(const Image* image, int32_t x, int32_t y, int32_t width, int32_t height) :
|
||||
image(image),
|
||||
x(x),
|
||||
y(y),
|
||||
width(width),
|
||||
height(height)
|
||||
{}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
if (image == nullptr || !image->is_valid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (x < 0 || y < 0 || width <= 0 || height <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return x + width <= image->get_width() && y + height <= image->get_height();
|
||||
}
|
||||
|
||||
uint32_t get_pixel(int32_t localX, int32_t localY) const
|
||||
{
|
||||
if (!is_valid() || localX < 0 || localX >= width || localY < 0 || localY >= height)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return image->get_pixel(x + localX, y + localY);
|
||||
}
|
||||
|
||||
uint32_t get_pixel_fast(int32_t localX, int32_t localY) const
|
||||
{
|
||||
return image->get_pixel_fast(x + localX, y + localY);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user