textures.rin
src/1.getting_started/4.1.textures/textures.rin · 240 lines
// 4.1 Textures
//
// An image on a rectangle. Each vertex says where in the image it sits, its
// texture coordinate, 0..1 across and down, and the pixel shader samples the
// image there. Two textures are blended: the crate, with 20% of the face.
//
// load_texture decodes the file with includes/image.rin (WIC, Windows' own
// codecs) into RGBA8 pixels, uploads them, and has the GPU generate the
// mipmaps from the first level. A sampler, a separate object in D3D, decides
// how the texture is filtered and what happens past its edges.
import "../../../includes/win32.rin"
import "../../../includes/math.rin"
import "../../../includes/image.rin"
import "../../../includes/learn/shader.rin"
import "std/print.rin"
// settings
scr_width: i32 = 1280;
scr_height: i32 = 720;
// x, y, z, then u, v. D3D's texture coordinate (0, 0) is the image's
// top-left, where GL's is its bottom-left, so nothing is flipped on load.
rectangle_vertices: [20]f32 = {
-0.5, -0.5, 0.0, 0.0, 1.0,
0.5, -0.5, 0.0, 1.0, 1.0,
0.5, 0.5, 0.0, 1.0, 0.0,
-0.5, 0.5, 0.0, 0.0, 0.0,
};
rectangle_indices: [6]u32 = { 0, 1, 2, 0, 2, 3 };
layout: [2]D3D11_INPUT_ELEMENT_DESC = {
{ .SemanticName = "POSITION", .Format = DXGI_FORMAT.DXGI_FORMAT_R32G32B32_FLOAT, .AlignedByteOffset = 0 },
{ .SemanticName = "TEXCOORD", .Format = DXGI_FORMAT.DXGI_FORMAT_R32G32_FLOAT, .AlignedByteOffset = 12 },
};
// win32: every event for the window (keys, mouse, closing) arrives here
// ---------------------------------------------------------------------
window_proc: proc[callconv(CALLBACK)](hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> LRESULT = {
if (msg == WM_DESTROY) {
PostQuitMessage(0);
return 0;
}
return DefWindowProcA(hwnd, msg, wparam, lparam);
}
// process all input: ask Windows whether relevant keys are down this frame and react accordingly
// ----------------------------------------------------------------------------------------------
// The top bit of GetAsyncKeyState is set while a key is held, which makes the SHORT negative.
key_down: proc(key: i32) -> b32 = {
return GetAsyncKeyState(key) < 0;
}
process_input: proc(window: HWND) -> void = {
// GetAsyncKeyState reads the whole keyboard, so ignore it while another
// window has the focus; glfwGetKey only ever sees its own window's keys.
if (GetForegroundWindow() != window) { return; }
if (key_down(VK_ESCAPE)) { DestroyWindow(window); }
}
// An immutable buffer: filled once at creation, never written by the CPU again.
create_buffer: proc(device: *ID3D11Device, bind: UINT, data: *const void, size: u64) -> *ID3D11Buffer = {
desc: D3D11_BUFFER_DESC = {
.ByteWidth = cast(size, UINT),
.Usage = D3D11_USAGE.D3D11_USAGE_IMMUTABLE,
.BindFlags = bind,
};
init: D3D11_SUBRESOURCE_DATA = { .pSysMem = data };
buffer: *ID3D11Buffer = null;
ID3D11Device_CreateBuffer(device, desc&, init&, buffer&);
return buffer;
}
// utility function for loading a 2D texture from file, with a full mip chain
// --------------------------------------------------------------------------
// `srgb`: the file stores colours, gamma encoded, and sampling should decode
// them to linear. Data such as normals or roughness is linear already.
load_texture: proc(device: *ID3D11Device, context: *ID3D11DeviceContext, path: [*]const char, srgb: b32) -> *ID3D11ShaderResourceView = {
img: image = image_load(path);
if (img.pixels == null) {
printf("Texture failed to load at path: %s\n", path);
exit(1);
}
desc: D3D11_TEXTURE2D_DESC = {
.Width = cast(img.width, UINT),
.Height = cast(img.height, UINT),
.MipLevels = 0, // the whole chain
.ArraySize = 1,
.Format = srgb ? DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
.SampleDesc = { .Count = 1 },
// Generating mips draws into the texture, so it is a render target too.
.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET,
.MiscFlags = D3D11_RESOURCE_MISC_GENERATE_MIPS,
};
texture: *ID3D11Texture2D = null;
srv: *ID3D11ShaderResourceView = null;
ID3D11Device_CreateTexture2D(device, desc&, null, texture&);
ID3D11Device_CreateShaderResourceView(device, cast(texture, *ID3D11Resource), null, srv&);
ID3D11DeviceContext_UpdateSubresource(context, cast(texture, *ID3D11Resource), 0, null, img.pixels, cast(img.width * 4, UINT), 0);
ID3D11DeviceContext_GenerateMips(context, srv);
return srv;
}
main: proc() -> i32 = {
// win32: register a window class and create the window
// ----------------------------------------------------
SetProcessDPIAware();
instance: HMODULE = GetModuleHandleA(null);
wc: WNDCLASSA = {
.lpfnWndProc = window_proc,
.hInstance = instance,
.hCursor = LoadCursorA(null, IDC_ARROW),
.lpszClassName = "LearnD3D11",
};
RegisterClassA(wc&);
// A fixed size: the swap chain and every render target keep the size they
// were created with, so nothing needs rebuilding on resize.
style: DWORD = WS_OVERLAPPEDWINDOW & ~(WS_THICKFRAME | WS_MAXIMIZEBOX);
rect: RECT = { .right = scr_width, .bottom = scr_height };
AdjustWindowRect(rect&, style, 0);
window: HWND = CreateWindowExA(0, wc.lpszClassName, "LearnD3D11", style | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT,
rect.right - rect.left, rect.bottom - rect.top, null, null, instance, null);
if (window == null) {
printf("Failed to create the window\n");
return -1;
}
// d3d11: create the device, its immediate context and a swap chain
// ----------------------------------------------------------------
swap_desc: DXGI_SWAP_CHAIN_DESC = {
.BufferDesc = {
.Width = cast(scr_width, UINT),
.Height = cast(scr_height, UINT),
.Format = DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM,
},
.SampleDesc = { .Count = 1 },
.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT,
.BufferCount = 2,
.OutputWindow = window,
.Windowed = 1,
.SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_DISCARD,
};
feature_level: D3D_FEATURE_LEVEL = D3D_FEATURE_LEVEL.D3D_FEATURE_LEVEL_11_0;
swap_chain: *IDXGISwapChain = null;
device: *ID3D11Device = null;
context: *ID3D11DeviceContext = null;
if (D3D11CreateDeviceAndSwapChain(null, D3D_DRIVER_TYPE.D3D_DRIVER_TYPE_HARDWARE, null, 0, feature_level&, 1, D3D11_SDK_VERSION,
swap_desc&, swap_chain&, device&, null, context&) < 0) {
printf("Failed to create the D3D11 device\n");
return -1;
}
backbuffer: *ID3D11Texture2D = null;
IDXGISwapChain_GetBuffer(swap_chain, 0, IID_ID3D11Texture2D&, cast(backbuffer&, **void));
backbuffer_rtv: *ID3D11RenderTargetView = null;
ID3D11Device_CreateRenderTargetView(device, cast(backbuffer, *ID3D11Resource), null, backbuffer_rtv&);
// configure global d3d11 state
// ----------------------------
// GL's conventions rather than D3D's: counter-clockwise triangles face the
// viewer, and nothing is culled.
raster_desc: D3D11_RASTERIZER_DESC = {
.FillMode = D3D11_FILL_MODE.D3D11_FILL_SOLID,
.CullMode = D3D11_CULL_MODE.D3D11_CULL_NONE,
.FrontCounterClockwise = 1,
.DepthClipEnable = 1,
};
raster_state: *ID3D11RasterizerState = null;
ID3D11Device_CreateRasterizerState(device, raster_desc&, raster_state&);
ID3D11DeviceContext_RSSetState(context, raster_state);
viewport: D3D11_VIEWPORT = {
.Width = cast(scr_width, f32),
.Height = cast(scr_height, f32),
.MaxDepth = 1.0,
};
// A sampler: how textures are filtered and what happens past their edges.
sampler_desc: D3D11_SAMPLER_DESC = {
.Filter = D3D11_FILTER.D3D11_FILTER_ANISOTROPIC,
.AddressU = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
.AddressV = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
.AddressW = D3D11_TEXTURE_ADDRESS_MODE.D3D11_TEXTURE_ADDRESS_WRAP,
.MaxAnisotropy = 8,
.MaxLOD = D3D11_FLOAT32_MAX,
};
sampler: *ID3D11SamplerState = null;
ID3D11Device_CreateSamplerState(device, sampler_desc&, sampler&);
ID3D11DeviceContext_PSSetSamplers(context, 0, 1, sampler&);
// build and compile our shader program
// ------------------------------------
s: shader = shader_create(device, filesystem_path("src/1.getting_started/4.1.textures/textures.hlsl"), layout);
// set up vertex data and textures
// -------------------------------
vb: *ID3D11Buffer = create_buffer(device, D3D11_BIND_VERTEX_BUFFER, rectangle_vertices[0]&, sizeof(rectangle_vertices));
ib: *ID3D11Buffer = create_buffer(device, D3D11_BIND_INDEX_BUFFER, rectangle_indices[0]&, sizeof(rectangle_indices));
stride: UINT = 20;
offset: UINT = 0;
textures: [2]*ID3D11ShaderResourceView = {
load_texture(device, context, filesystem_path("resources/textures/container.jpg"), 0),
load_texture(device, context, filesystem_path("resources/textures/awesomeface.png"), 0),
};
// render loop
// -----------
running: b32 = 1;
while (running) {
msg: MSG = {};
while (PeekMessageA(msg&, null, 0, 0, PM_REMOVE) != 0) {
if (msg.message == WM_QUIT) { running = 0; }
TranslateMessage(msg&);
DispatchMessageA(msg&);
}
// input
// -----
process_input(window);
// render
// ------
clear_color: [4]f32 = { 0.2, 0.3, 0.3, 1.0 };
ID3D11DeviceContext_OMSetRenderTargets(context, 1, backbuffer_rtv&, null);
ID3D11DeviceContext_RSSetViewports(context, 1, viewport&);
ID3D11DeviceContext_ClearRenderTargetView(context, backbuffer_rtv, clear_color[0]&);
// bind textures on corresponding texture registers, t0 and t1
ID3D11DeviceContext_PSSetShaderResources(context, 0, 2, textures[0]&);
shader_use(context, s&);
ID3D11DeviceContext_IASetPrimitiveTopology(context, D3D11_PRIMITIVE_TOPOLOGY.D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
ID3D11DeviceContext_IASetVertexBuffers(context, 0, 1, vb&, stride&, offset&);
ID3D11DeviceContext_IASetIndexBuffer(context, ib, DXGI_FORMAT.DXGI_FORMAT_R32_UINT, 0);
ID3D11DeviceContext_DrawIndexed(context, 6, 0, 0);
// dxgi: show the image just drawn, waiting for the next vertical blank
// --------------------------------------------------------------------
IDXGISwapChain_Present(swap_chain, 1, 0);
}
return 0;
}