当我第一次想到创建一个很小的曼德尔布洛特渲染器的时候,我就知道这是可能的,因为人们会制作一段时间的小演示,而且我可能会使用着色器。然而,在那个时候,我甚至不知道该如何开始,所以有一段时间,这只是在我的脑后,直到最近,当我终于学会了opengl,并意识到我最终知道我如何做它,结果我能够创建一个小小的Mandelbrot渲染器。我的主要问题是,在一般情况下,我可以以什么方式改进代码,以及如何使可执行文件变得更小,因为它目前是1538字节,我不知道如何使其更小?
main.c
// standard headers
#include
#include
// windows headers
#define WIN32_LEAN_AND_MEAN
#define WIN32_EXTRA_LEAN
#include
// opengl headers
#define WGL_WGLEXT_PROTOTYPES
#include
#include "glext.h"
#include "wglext.h"
#include "opengl.h"
// needed when we use floats
extern int _fltused;
int _fltused;
typedef struct Window
{
HDC device_context;
float aspect_ratio;
float scale, pos[2];
float smooth_scale, smooth_pos[2];
int32_t max_iterations;
} Window;
typedef enum Keys
{
KEY_W,
KEY_S,
KEY_A,
KEY_D,
KEY_PLUS1,
KEY_PLUS2,
KEY_MINUS1,
KEY_MINUS2,
KEY_UP,
KEY_DOWN,
KEY_R,
KEY_CTRL,
KEY_LENGTH // needed to keep track of number of keys
} Keys;
// NOTE: we could use GetWindowLongPtr and SetWindowLongPtr
// however this is much more easier
static Window global_window;
static bool keys[KEY_LENGTH];
static LRESULT CALLBACK WinProc(HWND window_handle, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_SYSKEYDOWN:
case WM_SYSCHAR:
case WM_SYSKEYUP:
{
} break;
case WM_SIZE:
{
// the width and height are stored in the low and high word of lParam respectively
LPARAM const width = lParam & 0xFFFF;
LPARAM const height = (lParam >> 16) & 0xFFFF;
// store the aspect ratio
global_window.aspect_ratio = (float)width / (float)height;
glViewport(0, 0, width, height);
} break;
case WM_QUIT:
case WM_CLOSE:
case WM_DESTROY:
{
ExitProcess(0);
}
case WM_KEYUP:
case WM_KEYDOWN:
{
bool const should_flip = ((lParam >> 30) & 0x1) == ((lParam >> 31) & 0x1);
if (should_flip)
{
switch (wParam)
{
case 'W':
{
keys[KEY_W] = !keys[KEY_W];
} break;
case 'S':
{
keys[KEY_S] = !keys[KEY_S];
} break;
case 'A':
{
keys[KEY_A] = !keys[KEY_A];
} break;
case 'D':
{
keys[KEY_D] = !keys[KEY_D];
} break;
case 'R':
{
keys[KEY_R] = !keys[KEY_R];
} break;
case VK_CONTROL:
{
keys[KEY_CTRL] = !keys[KEY_CTRL];
} break;
case VK_OEM_PLUS:
{
keys[KEY_PLUS1] = !keys[KEY_PLUS1];
} break;
case VK_ADD:
{
keys[KEY_PLUS2] = !keys[KEY_PLUS2];
} break;
case VK_OEM_MINUS:
{
keys[KEY_MINUS1] = !keys[KEY_MINUS1];
} break;
case VK_SUBTRACT:
{
keys[KEY_MINUS2] = !keys[KEY_MINUS2];
} break;
case VK_UP:
{
keys[KEY_UP] = !keys[KEY_UP];
} break;
case VK_DOWN:
{
keys[KEY_DOWN] = !keys[KEY_DOWN];
} break;
}
if (wParam == VK_ESCAPE) ExitProcess(0);
}
} break;
default:
{
return DefWindowProc(window_handle, message, wParam, lParam);
}
}
return 0;
}
static void create_opengl_context(HDC const device_context)
{
// same as:
// static PIXELFORMATDESCRIPTOR const pfd = {
// .nSize = sizeof(pfd),
// .dwFlags = PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW,
// .iPixelType = PFD_TYPE_RGBA,
// .cColorBits = 32,
// .cDepthBits = 32,
// .iLayerType = PFD_MAIN_PLANE
// };
SetPixelFormat(device_context, 9, NULL);
// create an opengl 3.3 context
HGLRC const opengl_context = wglCreateContext(device_context);
// make the new opengl context current and active
wglMakeCurrent(device_context, opengl_context);
PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = (PFNWGLSWAPINTERVALEXTPROC)
wglGetProcAddress("wglSwapIntervalEXT");
wglSwapIntervalEXT(0);
}
static void create_window(int32_t width, int32_t height)
{
// create a window class
static WNDCLASSA const wndclass = {
.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
.lpfnWndProc = &WinProc,
.lpszClassName = "0",
};
RegisterClassA(&wndclass);
// create a window
HWND const window_handle = CreateWindowA(wndclass.lpszClassName,
"",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
width, height, NULL, NULL,
NULL, NULL);
// create a device context
HDC const device_context = GetDC(window_handle);
// create an opengl context
create_opengl_context(device_context);
// load opengl extensions after creating an opengl context
load_extensions();
// setup global window
{
global_window.device_context = device_context;
global_window.aspect_ratio = (float)width / (float)height;
global_window.scale = 1.0f;
global_window.smooth_scale = 0.5f;
global_window.max_iterations = 200;
}
// show the window
ShowWindow(window_handle, SW_SHOWDEFAULT);
}
static unsigned int compile_shaders(char const *vertex_shader_source,
char const *fragment_shader_source)
{
// compile vertex shader
unsigned int vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &vertex_shader_source, NULL);
glCompileShader(vertex_shader);
// compile fragment shader
unsigned int fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &fragment_shader_source, NULL);
glCompileShader(fragment_shader);
// only needed for debugging
#ifdef DEBUG_MODE
{
int success;
char info_log[512];
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
if(success == GL_FALSE)
{
glGetShaderInfoLog(fragment_shader, sizeof(info_log),
NULL, info_log);
WriteFile((HANDLE)(STD_OUTPUT_HANDLE),
info_log, (DWORD)lstrlenA(info_log),
NULL, NULL);
}
}
#endif
// link the shaders
unsigned int shader_program = glCreateProgram();
glAttachShader(shader_program, vertex_shader);
glAttachShader(shader_program, fragment_shader);
glLinkProgram(shader_program);
return shader_program;
}
static float lerp(float v0, float v1, float t)
{
return (1.0f - t) * v0 + t * v1;
}
__declspec(noreturn) void __stdcall entry(void)
{
create_window(800, 600);
// by making this smaller we can save space at the cost of readabilty
#define VERTEX_SHADER \
"#version 330\n" \
"out vec2 u;void main(){u=vec2[](vec2(0),vec2(1,0),vec2(0,1),vec2(1))[gl_VertexID];" \
"gl_Position=vec4(vec2[](vec2(-1,-1),vec2(1,-1),vec2(-1,1),vec2(1))[gl_VertexID],0,1);}" \
#define FRAGMENT_SHADER \
"#version 330\n" \
"#define B 200000.0\n" \
"out vec4 F;in vec2 u;uniform int I;uniform float A;uniform vec4 D;" \
"void main(){vec2 c=((u*2-1)*vec2(A,1)*D.y-D.zw);vec2 z=vec2(0);int i;" \
"for(i=0;i 2)
{
global_window.max_iterations -= 1;
}
}
}
}opengl.h
#ifndef OPENGL_H
#define OPENGL_H
/* from http://dantefalcone.name/tutorials/1a-windows-win32-window-and-3d-context-creation/ */
// Program
static PFNGLCREATEPROGRAMPROC glCreateProgram;
static PFNGLUSEPROGRAMPROC glUseProgram;
static PFNGLATTACHSHADERPROC glAttachShader;
static PFNGLLINKPROGRAMPROC glLinkProgram;
static PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
static PFNGLUNIFORM1IPROC glUniform1i;
static PFNGLUNIFORM1FPROC glUniform1f;
static PFNGLUNIFORM4FPROC glUniform4f;
// Shader
static PFNGLCREATESHADERPROC glCreateShader;
static PFNGLSHADERSOURCEPROC glShaderSource;
static PFNGLCOMPILESHADERPROC glCompileShader;
// for debuging
static PFNGLGETSHADERIVPROC glGetShaderiv;
static PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
// we need to load opengl extensions from opengl32.dll
static void load_extensions(void)
{
// Program
glCreateProgram = (PFNGLCREATEPROGRAMPROC)wglGetProcAddress("glCreateProgram");
glUseProgram = (PFNGLUSEPROGRAMPROC)wglGetProcAddress("glUseProgram");
glAttachShader = (PFNGLATTACHSHADERPROC)wglGetProcAddress("glAttachShader");
glLinkProgram = (PFNGLLINKPROGRAMPROC)wglGetProcAddress("glLinkProgram");
glGetUniformLocation = (PFNGLGETUNIFORMLOCATIONPROC)wglGetProcAddress("glGetUniformLocation");
glUniform1i = (PFNGLUNIFORM1IPROC)wglGetProcAddress("glUniform1i");
glUniform1f = (PFNGLUNIFORM1FPROC)wglGetProcAddress("glUniform1f");
glUniform4f = (PFNGLUNIFORM4FPROC)wglGetProcAddress("glUniform4f");
// Shader
glCreateShader = (PFNGLCREATESHADERPROC)wglGetProcAddress("glCreateShader");
glShaderSource = (PFNGLSHADERSOURCEPROC)wglGetProcAddress("glShaderSource");
glCompileShader = (PFNGLCOMPILESHADERPROC)wglGetProcAddress("glCompileShader");
#ifdef DEBUG_MODE
glGetShaderInfoLog = (PFNGLGETSHADERINFOLOGPROC)wglGetProcAddress("glGetShaderInfoLog");
glGetShaderiv = (PFNGLGETSHADERIVPROC)wglGetProcAddress("glGetShaderiv");
#endif
}
#endif // OPENGL_Hwglext.h https://www.khronos.org/registry/OpenGL/api/GL/wglext.h
glext.h https://www.khronos.org/registry/OpenGL/api/GL/glext.h
建造用途:
Makefile:
NAME = prog
CC = clang-cl # if you don't have clang-cl use cl instead and change FLAGS
FLAGS = -m32 -W4 -c -nologo -GS- -Gr -Ofast -Oi -Gs9999999
LINK_FLAGS = /UNSAFEIMPORT /TRUNCATEFLOATS:18 /HASHSIZE:10 main.obj \
kernel32.lib user32.lib shell32.lib gdi32.lib opengl32.lib \
/SUBSYSTEM:windows /NODEFAULTLIB /ENTRY:entry /OUT:"$(NAME).exe" /STACK:0x100000,0x100000
all: main.c
$(CC) $(FLAGS) main.c && Crinkler $(LINK_FLAGS)
clean:
del $(NAME).exe要构建,您还需要下载Crinkler https://github.com/runestubbe/Crinkler
如下所示:

发布于 2020-12-18 19:13:58
首先,请注意,对程序进行超优化以达到最小可执行大小的重大努力实际上是码高尔夫的领域;Code关注的是遵循最佳实践的结构良好、可靠的代码。如果前者妨碍了后者,您会发现这个站点上的建议倾向于“好代码”而不是“小代码”。
为此目的:
extern int _fltused;
int _fltused;为什么?您还没有将其标记为static,因此应该已经导出了。在一个不同的翻译单元包含的头文件中,声明extern会更有用,这不是这里发生的事情;所以,只需要int _fltused = 0;。
虚拟密钥码适合于一个字节。考虑将switch (wParam)替换为一个256字节长的对应表,其中索引是虚拟密钥代码,值是您的enum Keys代码。
更好的是,让keys本身长256个字节,完全取消enum,并假设索引映射到虚拟密钥代码。确保范围-检查任何传入的VKs只是为了确保一个超出范围的取消是不可能的。
而bool const 是等价的 to const bool,后者则更为常见。
对于load_extensions,传统的做法是把它放在一个C文件中。这将是更好的结构,不应影响编译大小的正确配置的编译器和链接器。
https://codereview.stackexchange.com/questions/253160
复制相似问题