Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
screenshot.cpp
Go to the documentation of this file.
1// screenshot.cpp — バックバッファを BMP に書く。使い方は screenshot.h。
2#include "render/screenshot.h"
3
4#include <array>
5#include <filesystem>
6#include <fstream>
7#include <system_error>
8#include <vector>
9
10#include "core/aliases.h"
11#include "core/unicode.h"
12#include "gpu/resources.h"
13
14namespace bicameral::render {
15 namespace {
16
17 constexpr uint32_t BYTES_PER_PIXEL = 4;
18 constexpr uint32_t FILE_HEADER_BYTES = 14;
19 constexpr uint32_t INFO_HEADER_BYTES = 40;
20
21 // 小さい方から順に書く(BMP の数はすべてリトルエンディアン)
22 void AppendLittleEndian(std::vector<uint8_t>& bytes, uint32_t value, uint32_t size) {
23 for (uint32_t index = 0; index < size; ++index)
24 bytes.push_back(static_cast<uint8_t>(value >> (8 * index)));
25 }
26
27 // BITMAPFILEHEADER + BITMAPINFOHEADER(高さを負にして上から下の並び)
28 std::vector<uint8_t> BmpHeader(uint32_t width, uint32_t height) {
29 const uint32_t pixelBytes = width * height * BYTES_PER_PIXEL;
30 std::vector<uint8_t> header;
31 header.push_back('B');
32 header.push_back('M');
34 AppendLittleEndian(header, 0, 4); // 予約
37 AppendLittleEndian(header, width, 4);
38 AppendLittleEndian(header, static_cast<uint32_t>(-static_cast<int32_t>(height)), 4);
39 AppendLittleEndian(header, 1, 2); // 面
40 AppendLittleEndian(header, 32, 2); // bit/画素
41 AppendLittleEndian(header, 0, 4); // BI_RGB(圧縮なし)
42 AppendLittleEndian(header, pixelBytes, 4);
43 AppendLittleEndian(header, 2835, 4); // 72 dpi
44 AppendLittleEndian(header, 2835, 4);
45 AppendLittleEndian(header, 0, 4);
46 AppendLittleEndian(header, 0, 4);
47
48 return header;
49 }
50
51 } // namespace
52
53 std::expected<ScreenshotCapture, std::string> ScreenshotCapture::Create(ID3D12Device* device,
54 ID3D12Resource* backBuffer) {
55 ScreenshotCapture capture;
56 const D3D12_RESOURCE_DESC desc = backBuffer->GetDesc();
57 if (desc.Format != DXGI_FORMAT_R8G8B8A8_UNORM)
58 return std::unexpected("画像に書けるのは RGBA8 のバックバッファだけ");
59
60 uint64_t totalBytes = 0;
61 device->GetCopyableFootprints(&desc, 0, 1, 0, &capture.m_footprint, nullptr, nullptr, &totalBytes);
62 capture.m_width = static_cast<uint32_t>(desc.Width);
63 capture.m_height = desc.Height;
64 capture.m_readback = gpu::CreateBuffer(device, totalBytes, gpu::BufferKind::Readback);
65
66 if (!capture.m_readback ||
67 FAILED(
68 device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&capture.m_allocator))) ||
69 FAILED(device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, capture.m_allocator.Get(), nullptr,
70 IID_PPV_ARGS(&capture.m_list)))) {
71 return std::unexpected("画像の読み戻しを作れない");
72 }
73
74 capture.m_readback->SetName(L"Screenshot.readback");
75 if (FAILED(capture.m_list->Close()))
76 return std::unexpected("画像のリストを閉じられない");
77
78 return capture;
79 }
80
81 ID3D12CommandList* ScreenshotCapture::Record(ID3D12Resource* backBuffer) {
82 if (FAILED(m_allocator->Reset()) || FAILED(m_list->Reset(m_allocator.Get(), nullptr)))
83 return nullptr;
84
85 const D3D12_RESOURCE_BARRIER toCopy = gpu::Transition(backBuffer, D3D12_RESOURCE_STATE_PRESENT,
86 D3D12_RESOURCE_STATE_COPY_SOURCE);
87 m_list->ResourceBarrier(1, &toCopy);
88
89 const D3D12_TEXTURE_COPY_LOCATION destination{.pResource = m_readback.Get(),
90 .Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT,
91 .PlacedFootprint = m_footprint};
92
93 const D3D12_TEXTURE_COPY_LOCATION source{
94 .pResource = backBuffer, .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, .SubresourceIndex = 0};
95 m_list->CopyTextureRegion(&destination, 0, 0, 0, &source, nullptr);
96 const D3D12_RESOURCE_BARRIER toPresent = gpu::Transition(backBuffer, D3D12_RESOURCE_STATE_COPY_SOURCE,
97 D3D12_RESOURCE_STATE_PRESENT);
98 m_list->ResourceBarrier(1, &toPresent);
99
100 return SUCCEEDED(m_list->Close()) ? m_list.Get() : nullptr;
101 }
102
103 std::expected<void, std::string> ScreenshotCapture::WriteBmp(const fs::path& path) const {
104 void* mapped = nullptr;
105 if (FAILED(m_readback->Map(0, nullptr, &mapped)))
106 return std::unexpected("画像の読み戻しを開けない");
107
108 const auto* source = static_cast<const uint8_t*>(mapped);
109 std::vector<uint8_t> file = BmpHeader(m_width, m_height);
110 file.reserve(file.size() + size_t{m_width} * m_height * BYTES_PER_PIXEL);
111
112 for (uint32_t row = 0; row < m_height; ++row) {
113 const uint8_t* line = source + m_footprint.Offset + size_t{row} * m_footprint.Footprint.RowPitch;
114 for (uint32_t column = 0; column < m_width; ++column) {
115 const uint8_t* pixel = line + size_t{column} * BYTES_PER_PIXEL;
116 file.insert(file.end(), {pixel[2], pixel[1], pixel[0], 255}); // RGBA → BGRA
117 }
118 }
119
120 const D3D12_RANGE noWrite{};
121 m_readback->Unmap(0, &noWrite);
122
123 // 置き場所のフォルダが無ければ作る(テストはビルドのフォルダの images/ に書く。T-0025)
124 std::error_code error;
125 if (path.has_parent_path())
126 fs::create_directories(path.parent_path(), error);
127
128 std::ofstream stream(path, std::ios::binary);
129 if (!stream)
130 return std::unexpected("画像のファイルを開けない: " + ToUtf8(path.wstring()));
131
132 stream.write(reinterpret_cast<const char*>(file.data()), static_cast<std::streamsize>(file.size()));
133 if (!stream)
134 return std::unexpected("画像のファイルに書けない");
135
136 return {};
137 }
138
139} // namespace bicameral::render
std::expected< void, std::string > WriteBmp(const fs::path &path) const
static std::expected< ScreenshotCapture, std::string > Create(ID3D12Device *device, ID3D12Resource *backBuffer)
ComPtr< ID3D12GraphicsCommandList > m_list
Definition screenshot.h:35
D3D12_PLACED_SUBRESOURCE_FOOTPRINT m_footprint
Definition screenshot.h:39
ComPtr< ID3D12CommandAllocator > m_allocator
Definition screenshot.h:34
ComPtr< ID3D12Resource > m_readback
Definition screenshot.h:38
ID3D12CommandList * Record(ID3D12Resource *backBuffer)
D3D12_RESOURCE_BARRIER Transition(ID3D12Resource *resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after)
Definition resources.cpp:65
ComPtr< ID3D12Resource > CreateBuffer(ID3D12Device *device, uint64_t sizeBytes, BufferKind kind)
Definition resources.cpp:18
std::vector< uint8_t > BmpHeader(uint32_t width, uint32_t height)
void AppendLittleEndian(std::vector< uint8_t > &bytes, uint32_t value, uint32_t size)
std::string ToUtf8(std::wstring_view text)
Definition unicode.cpp:10