some new stuff.
idk its all pretty fun! some C++ too!
This commit is contained in:
21
simulations/mandelbrotset/CppProperties.json
Normal file
21
simulations/mandelbrotset/CppProperties.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"configurations": [
|
||||
{
|
||||
"inheritEnvironments": [
|
||||
"msvc_x86"
|
||||
],
|
||||
"name": "x86-Debug",
|
||||
"includePath": [
|
||||
"${env.INCLUDE}",
|
||||
"${workspaceRoot}\\**"
|
||||
],
|
||||
"defines": [
|
||||
"WIN32",
|
||||
"_DEBUG",
|
||||
"UNICODE",
|
||||
"_UNICODE"
|
||||
],
|
||||
"intelliSenseMode": "windows-msvc-x86"
|
||||
}
|
||||
]
|
||||
}
|
||||
22
simulations/mandelbrotset/cpp/main.cpp
Normal file
22
simulations/mandelbrotset/cpp/main.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include <windows.h>
|
||||
#include "mandelbrot_app.h"
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
|
||||
// Check for DirectX 11 support
|
||||
if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0,
|
||||
D3D11_SDK_VERSION, nullptr, nullptr, nullptr))) {
|
||||
MessageBox(nullptr, L"DirectX 11 is not available on this system.", L"Error", MB_OK | MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
try {
|
||||
MandelbrotApp app(hInstance);
|
||||
app.Run();
|
||||
}
|
||||
catch (...) {
|
||||
MessageBox(nullptr, L"An unexpected error occurred.", L"Error", MB_OK | MB_ICONERROR);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
927
simulations/mandelbrotset/cpp/mandelbrot_app.cpp
Normal file
927
simulations/mandelbrotset/cpp/mandelbrot_app.cpp
Normal file
@@ -0,0 +1,927 @@
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <wrl/client.h>
|
||||
#include <algorithm>
|
||||
#include <tchar.h>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include "mandelbrot_app.h"
|
||||
|
||||
#ifndef GET_X_LPARAM
|
||||
#define GET_X_LPARAM(lParam) ((int)(short)LOWORD(lParam))
|
||||
#endif
|
||||
|
||||
#ifndef GET_Y_LPARAM
|
||||
#define GET_Y_LPARAM(lParam) ((int)(short)HIWORD(lParam))
|
||||
#endif
|
||||
|
||||
const char* computeShaderSource = R"(
|
||||
cbuffer CB : register(b0) {
|
||||
float xmin, xmax, ymin, ymax;
|
||||
int width, height, maxIter;
|
||||
float time;
|
||||
};
|
||||
|
||||
RWTexture2D<float4> Output : register(u0);
|
||||
|
||||
float3 palette(float t, int scheme) {
|
||||
if (scheme == 0) {
|
||||
float3 a = float3(0.5, 0.5, 0.5);
|
||||
float3 b = float3(0.5, 0.5, 0.5);
|
||||
float3 c = float3(1.0, 1.0, 1.0);
|
||||
float3 d = float3(0.0, 0.33, 0.67);
|
||||
return a + b * cos(6.28318 * (c * t + d));
|
||||
} else if (scheme == 1) {
|
||||
float3 a = float3(0.5, 0.5, 0.0);
|
||||
float3 b = float3(0.5, 0.5, 0.0);
|
||||
float3 c = float3(1.0, 0.7, 0.4);
|
||||
float3 d = float3(0.0, 0.15, 0.20);
|
||||
return a + b * cos(6.28318 * (c * t + d));
|
||||
} else if (scheme == 2) {
|
||||
float3 a = float3(0.2, 0.5, 0.8);
|
||||
float3 b = float3(0.2, 0.4, 0.2);
|
||||
float3 c = float3(2.0, 1.0, 1.0);
|
||||
float3 d = float3(0.0, 0.25, 0.25);
|
||||
return a + b * cos(6.28318 * (c * t + d));
|
||||
} else if (scheme == 3) {
|
||||
float3 a = float3(0.5, 0.2, 0.8);
|
||||
float3 b = float3(0.5, 0.5, 0.5);
|
||||
float3 c = float3(1.0, 1.0, 0.5);
|
||||
float3 d = float3(0.8, 0.9, 0.3);
|
||||
return a + b * cos(6.28318 * (c * t + d));
|
||||
} else {
|
||||
float v = 0.5 + 0.5 * cos(6.28318 * t);
|
||||
return float3(v, v, v);
|
||||
}
|
||||
}
|
||||
|
||||
[numthreads(8, 8, 1)]
|
||||
void main(uint3 DTid : SV_DispatchThreadID) {
|
||||
int x = DTid.x;
|
||||
int y = DTid.y;
|
||||
|
||||
if (x >= width || y >= height) return;
|
||||
|
||||
float cx = xmin + (xmax - xmin) * x / (float)width;
|
||||
float cy = ymin + (ymax - ymin) * y / (float)height;
|
||||
|
||||
float zx = 0.0;
|
||||
float zy = 0.0;
|
||||
float zx2 = 0.0;
|
||||
float zy2 = 0.0;
|
||||
int iter = 0;
|
||||
|
||||
while (iter < maxIter && (zx2 + zy2) < 4.0) {
|
||||
zy = 2.0 * zx * zy + cy;
|
||||
zx = zx2 - zy2 + cx;
|
||||
zx2 = zx * zx;
|
||||
zy2 = zy * zy;
|
||||
iter++;
|
||||
}
|
||||
|
||||
float4 color;
|
||||
if (iter == maxIter) {
|
||||
color = float4(0.0, 0.0, 0.0, 1.0);
|
||||
} else {
|
||||
float log_zn = log(zx2 + zy2) * 0.5;
|
||||
float nu = log2(log_zn);
|
||||
float smooth_iter = iter + 1.0 - nu;
|
||||
|
||||
int scheme = maxIter >> 16;
|
||||
int actualMaxIter = maxIter & 0xFFFF;
|
||||
|
||||
float t = smooth_iter / 50.0 + time * 0.02;
|
||||
float3 rgb = palette(t, scheme);
|
||||
|
||||
float brightness = 0.5 + 0.5 * sin(smooth_iter * 0.1);
|
||||
rgb *= brightness;
|
||||
|
||||
color = float4(rgb, 1.0);
|
||||
}
|
||||
|
||||
Output[uint2(x, y)] = color;
|
||||
}
|
||||
)";
|
||||
|
||||
const char* vertexShaderSource = R"(
|
||||
struct VS_OUTPUT {
|
||||
float4 pos : SV_POSITION;
|
||||
float2 tex : TEXCOORD0;
|
||||
};
|
||||
|
||||
VS_OUTPUT main(uint id : SV_VertexID) {
|
||||
VS_OUTPUT output;
|
||||
output.tex = float2((id << 1) & 2, id & 2);
|
||||
output.pos = float4(output.tex * float2(2, -2) + float2(-1, 1), 0, 1);
|
||||
return output;
|
||||
}
|
||||
)";
|
||||
|
||||
const char* pixelShaderSource = R"(
|
||||
Texture2D tex : register(t0);
|
||||
SamplerState samp : register(s0);
|
||||
|
||||
float4 main(float4 pos : SV_POSITION, float2 texCoord : TEXCOORD0) : SV_TARGET {
|
||||
return tex.Sample(samp, texCoord);
|
||||
}
|
||||
)";
|
||||
|
||||
MandelbrotApp::MandelbrotApp(HINSTANCE hInstance) : hInstance_(hInstance) {
|
||||
QueryPerformanceFrequency(&perfFreq_);
|
||||
QueryPerformanceCounter(&lastFrameTime_);
|
||||
|
||||
CreateMainWindow();
|
||||
if (hwnd_) {
|
||||
InitD3D();
|
||||
CreateComputeShader();
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
MandelbrotApp::~MandelbrotApp() {
|
||||
if (context_) context_->ClearState();
|
||||
}
|
||||
|
||||
void MandelbrotApp::CreateMainWindow() {
|
||||
WNDCLASSEX wc = {};
|
||||
wc.cbSize = sizeof(WNDCLASSEX);
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = hInstance_;
|
||||
wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||||
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wc.lpszClassName = _T("MandelbrotViewerGPU");
|
||||
|
||||
if (!RegisterClassEx(&wc)) {
|
||||
MessageBox(nullptr, L"Failed to register window class", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
RECT rc = { 0, 0, 1280, 720 };
|
||||
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
|
||||
hwnd_ = CreateWindowEx(
|
||||
0,
|
||||
_T("MandelbrotViewerGPU"),
|
||||
_T("GPU Mandelbrot Viewer"),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
rc.right - rc.left, rc.bottom - rc.top,
|
||||
nullptr, nullptr, hInstance_, this
|
||||
);
|
||||
|
||||
if (!hwnd_) {
|
||||
MessageBox(nullptr, L"Failed to create window", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowWindow(hwnd_, SW_SHOWDEFAULT);
|
||||
UpdateWindow(hwnd_);
|
||||
}
|
||||
|
||||
void MandelbrotApp::InitD3D() {
|
||||
RECT rc;
|
||||
GetClientRect(hwnd_, &rc);
|
||||
|
||||
imageWidth_ = rc.right - rc.left;
|
||||
imageHeight_ = rc.bottom - rc.top;
|
||||
if (imageWidth_ < 1) imageWidth_ = 1;
|
||||
if (imageHeight_ < 1) imageHeight_ = 1;
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC scd = {};
|
||||
scd.BufferCount = 2;
|
||||
scd.BufferDesc.Width = imageWidth_;
|
||||
scd.BufferDesc.Height = imageHeight_;
|
||||
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
scd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
scd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
scd.OutputWindow = hwnd_;
|
||||
scd.SampleDesc.Count = 1;
|
||||
scd.SampleDesc.Quality = 0;
|
||||
scd.Windowed = TRUE;
|
||||
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||
|
||||
D3D_FEATURE_LEVEL featureLevels[] = {
|
||||
D3D_FEATURE_LEVEL_11_0,
|
||||
D3D_FEATURE_LEVEL_10_1,
|
||||
D3D_FEATURE_LEVEL_10_0
|
||||
};
|
||||
UINT numFeatureLevels = ARRAYSIZE(featureLevels);
|
||||
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
UINT flags = 0;
|
||||
|
||||
HRESULT hr = D3D11CreateDeviceAndSwapChain(
|
||||
nullptr,
|
||||
D3D_DRIVER_TYPE_HARDWARE,
|
||||
nullptr,
|
||||
flags,
|
||||
featureLevels,
|
||||
numFeatureLevels,
|
||||
D3D11_SDK_VERSION,
|
||||
&scd,
|
||||
&swapChain_,
|
||||
&device_,
|
||||
&featureLevel,
|
||||
&context_
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create D3D11 device and swap chain", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
ComPtr<ID3D11Texture2D> backBuffer;
|
||||
hr = swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to get swap chain back buffer", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
hr = device_->CreateRenderTargetView(backBuffer.Get(), nullptr, &renderTargetView_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create render target view", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
D3D11_SAMPLER_DESC sampDesc = {};
|
||||
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
|
||||
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
|
||||
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
|
||||
sampDesc.MinLOD = 0;
|
||||
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
|
||||
|
||||
hr = device_->CreateSamplerState(&sampDesc, &samplerState_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create sampler state", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the viewport
|
||||
D3D11_VIEWPORT vp;
|
||||
vp.Width = (float)imageWidth_;
|
||||
vp.Height = (float)imageHeight_;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = 0;
|
||||
vp.TopLeftY = 0;
|
||||
context_->RSSetViewports(1, &vp);
|
||||
|
||||
// Set render target
|
||||
context_->OMSetRenderTargets(1, renderTargetView_.GetAddressOf(), nullptr);
|
||||
|
||||
ResizeBuffers(imageWidth_, imageHeight_);
|
||||
}
|
||||
|
||||
void MandelbrotApp::CreateComputeShader() {
|
||||
if (!device_) return;
|
||||
|
||||
ComPtr<ID3DBlob> csBlob, errBlob;
|
||||
|
||||
HRESULT hr = D3DCompile(
|
||||
computeShaderSource,
|
||||
strlen(computeShaderSource),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
"main",
|
||||
"cs_5_0",
|
||||
D3DCOMPILE_ENABLE_STRICTNESS,
|
||||
0,
|
||||
&csBlob,
|
||||
&errBlob
|
||||
);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
if (errBlob) {
|
||||
OutputDebugStringA((char*)errBlob->GetBufferPointer());
|
||||
}
|
||||
MessageBox(nullptr, L"Failed to compile compute shader", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
hr = device_->CreateComputeShader(csBlob->GetBufferPointer(), csBlob->GetBufferSize(), nullptr, &computeShader_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create compute shader", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertex shader
|
||||
ComPtr<ID3DBlob> vsBlob;
|
||||
hr = D3DCompile(
|
||||
vertexShaderSource,
|
||||
strlen(vertexShaderSource),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
"main",
|
||||
"vs_5_0",
|
||||
D3DCOMPILE_ENABLE_STRICTNESS,
|
||||
0,
|
||||
&vsBlob,
|
||||
&errBlob
|
||||
);
|
||||
if (SUCCEEDED(hr)) {
|
||||
device_->CreateVertexShader(vsBlob->GetBufferPointer(), vsBlob->GetBufferSize(), nullptr, &vertexShader_);
|
||||
}
|
||||
else {
|
||||
MessageBox(nullptr, L"Failed to compile vertex shader", L"Error", MB_OK);
|
||||
}
|
||||
|
||||
// Pixel shader
|
||||
ComPtr<ID3DBlob> psBlob;
|
||||
hr = D3DCompile(
|
||||
pixelShaderSource,
|
||||
strlen(pixelShaderSource),
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
"main",
|
||||
"ps_5_0",
|
||||
D3DCOMPILE_ENABLE_STRICTNESS,
|
||||
0,
|
||||
&psBlob,
|
||||
&errBlob
|
||||
);
|
||||
if (SUCCEEDED(hr)) {
|
||||
device_->CreatePixelShader(psBlob->GetBufferPointer(), psBlob->GetBufferSize(), nullptr, &pixelShader_);
|
||||
}
|
||||
else {
|
||||
MessageBox(nullptr, L"Failed to compile pixel shader", L"Error", MB_OK);
|
||||
}
|
||||
|
||||
// Constant buffer - FIXED: Ensure proper alignment
|
||||
D3D11_BUFFER_DESC cbDesc = {};
|
||||
cbDesc.ByteWidth = (sizeof(ConstantBuffer) + 15) & ~15; // Align to 16 bytes
|
||||
cbDesc.Usage = D3D11_USAGE_DYNAMIC;
|
||||
cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
|
||||
cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
|
||||
cbDesc.MiscFlags = 0;
|
||||
cbDesc.StructureByteStride = 0;
|
||||
|
||||
hr = device_->CreateBuffer(&cbDesc, nullptr, &constantBuffer_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create constant buffer", L"Error", MB_OK);
|
||||
}
|
||||
}
|
||||
|
||||
void MandelbrotApp::ResizeBuffers(int width, int height) {
|
||||
if (width <= 0 || height <= 0 || !device_) return;
|
||||
|
||||
outputTexture_.Reset();
|
||||
outputUAV_.Reset();
|
||||
outputSRV_.Reset();
|
||||
|
||||
// Create output texture for compute shader
|
||||
D3D11_TEXTURE2D_DESC texDesc = {};
|
||||
texDesc.Width = width;
|
||||
texDesc.Height = height;
|
||||
texDesc.MipLevels = 1;
|
||||
texDesc.ArraySize = 1;
|
||||
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
texDesc.SampleDesc.Count = 1;
|
||||
texDesc.SampleDesc.Quality = 0;
|
||||
texDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||
texDesc.BindFlags = D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
|
||||
texDesc.CPUAccessFlags = 0;
|
||||
texDesc.MiscFlags = 0;
|
||||
|
||||
HRESULT hr = device_->CreateTexture2D(&texDesc, nullptr, &outputTexture_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create output texture", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create UAV for compute shader
|
||||
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc = {};
|
||||
uavDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE2D;
|
||||
uavDesc.Texture2D.MipSlice = 0;
|
||||
|
||||
hr = device_->CreateUnorderedAccessView(outputTexture_.Get(), &uavDesc, &outputUAV_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create UAV", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create SRV for pixel shader
|
||||
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
|
||||
srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
|
||||
srvDesc.Texture2D.MostDetailedMip = 0;
|
||||
srvDesc.Texture2D.MipLevels = 1;
|
||||
|
||||
hr = device_->CreateShaderResourceView(outputTexture_.Get(), &srvDesc, &outputSRV_);
|
||||
if (FAILED(hr)) {
|
||||
MessageBox(nullptr, L"Failed to create SRV", L"Error", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
imageWidth_ = width;
|
||||
imageHeight_ = height;
|
||||
}
|
||||
|
||||
void MandelbrotApp::UpdateTitle() {
|
||||
double zoom = 4.0 / (xmax_ - xmin_);
|
||||
std::wostringstream title;
|
||||
title << L"Mandelbrot Viewer | Zoom: " << std::fixed << std::setprecision(2) << zoom
|
||||
<< L"x | Iter: " << maxIter_
|
||||
<< L" | Color: " << colorScheme_ + 1
|
||||
<< L" | Anim: " << (animationEnabled_ ? L"ON" : L"OFF")
|
||||
<< L" | [H for Help]";
|
||||
SetWindowText(hwnd_, title.str().c_str());
|
||||
}
|
||||
|
||||
void MandelbrotApp::SaveBookmark() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
if (!bookmarks_[i].saved || i == 0) {
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
bookmarks_[i].xmin = xmin_;
|
||||
bookmarks_[i].xmax = xmax_;
|
||||
bookmarks_[i].ymin = ymin_;
|
||||
bookmarks_[i].ymax = ymax_;
|
||||
bookmarks_[i].maxIter = maxIter_;
|
||||
bookmarks_[i].saved = true;
|
||||
|
||||
std::wostringstream msg;
|
||||
msg << L"Bookmark saved to slot " << i;
|
||||
MessageBox(hwnd_, msg.str().c_str(), L"Bookmark", MB_OK | MB_ICONINFORMATION);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MandelbrotApp::LoadBookmark(int slot) {
|
||||
if (slot >= 0 && slot < 10 && bookmarks_[slot].saved) {
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
xmin_ = bookmarks_[slot].xmin;
|
||||
xmax_ = bookmarks_[slot].xmax;
|
||||
ymin_ = bookmarks_[slot].ymin;
|
||||
ymax_ = bookmarks_[slot].ymax;
|
||||
maxIter_ = bookmarks_[slot].maxIter;
|
||||
UpdateTitle();
|
||||
}
|
||||
}
|
||||
|
||||
void MandelbrotApp::ResetView() {
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
xmin_ = -2.5; xmax_ = 1.5;
|
||||
ymin_ = -1.5; ymax_ = 1.5;
|
||||
maxIter_ = 256;
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
void MandelbrotApp::ToggleAnimation() {
|
||||
animationEnabled_ = !animationEnabled_;
|
||||
if (!animationEnabled_) {
|
||||
animTime_ = 0.0f;
|
||||
}
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
void MandelbrotApp::AdjustIterations(int delta) {
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
maxIter_ = std::max(64, std::min(8192, maxIter_ + delta));
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
void MandelbrotApp::CycleColorScheme() {
|
||||
colorScheme_ = (colorScheme_ + 1) % 5;
|
||||
UpdateTitle();
|
||||
}
|
||||
|
||||
void MandelbrotApp::Zoom(double factor, int centerX, int centerY) {
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
|
||||
double centerReal = xmin_ + (xmax_ - xmin_) * centerX / imageWidth_;
|
||||
double centerImag = ymin_ + (ymax_ - ymin_) * centerY / imageHeight_;
|
||||
|
||||
double width = (xmax_ - xmin_) * factor;
|
||||
double height = (ymax_ - ymin_) * factor;
|
||||
|
||||
xmin_ = centerReal - width * 0.5;
|
||||
xmax_ = centerReal + width * 0.5;
|
||||
ymin_ = centerImag - height * 0.5;
|
||||
ymax_ = centerImag + height * 0.5;
|
||||
}
|
||||
|
||||
void MandelbrotApp::Pan(int dx, int dy) {
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
|
||||
double deltaX = (xmax_ - xmin_) * dx / imageWidth_;
|
||||
double deltaY = (ymax_ - ymin_) * dy / imageHeight_;
|
||||
|
||||
xmin_ -= deltaX;
|
||||
xmax_ -= deltaX;
|
||||
ymin_ += deltaY;
|
||||
ymax_ += deltaY;
|
||||
}
|
||||
|
||||
void MandelbrotApp::RenderMandelbrot() {
|
||||
if (!device_ || !context_ || !swapChain_ || !computeShader_ ||
|
||||
!constantBuffer_ || !outputUAV_ || !renderTargetView_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ConstantBuffer cb;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(viewMutex_);
|
||||
|
||||
double zoom = 4.0 / (xmax_ - xmin_);
|
||||
int adaptiveMaxIter = maxIter_;
|
||||
if (zoom > 10) adaptiveMaxIter = std::max(maxIter_, 512);
|
||||
if (zoom > 100) adaptiveMaxIter = std::max(maxIter_, 1024);
|
||||
if (zoom > 1000) adaptiveMaxIter = std::max(maxIter_, 2048);
|
||||
|
||||
cb.xmin = (float)xmin_;
|
||||
cb.xmax = (float)xmax_;
|
||||
cb.ymin = (float)ymin_;
|
||||
cb.ymax = (float)ymax_;
|
||||
cb.width = imageWidth_;
|
||||
cb.height = imageHeight_;
|
||||
cb.maxIter = (colorScheme_ << 16) | (adaptiveMaxIter & 0xFFFF);
|
||||
cb.time = animTime_;
|
||||
}
|
||||
|
||||
// Update constant buffer
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
HRESULT hr = context_->Map(constantBuffer_.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
|
||||
if (SUCCEEDED(hr)) {
|
||||
memcpy(mapped.pData, &cb, sizeof(ConstantBuffer));
|
||||
context_->Unmap(constantBuffer_.Get(), 0);
|
||||
}
|
||||
|
||||
// Run compute shader
|
||||
context_->CSSetShader(computeShader_.Get(), nullptr, 0);
|
||||
context_->CSSetConstantBuffers(0, 1, constantBuffer_.GetAddressOf());
|
||||
context_->CSSetUnorderedAccessViews(0, 1, outputUAV_.GetAddressOf(), nullptr);
|
||||
|
||||
UINT dispatchX = (imageWidth_ + 7) / 8;
|
||||
UINT dispatchY = (imageHeight_ + 7) / 8;
|
||||
context_->Dispatch(dispatchX, dispatchY, 1);
|
||||
|
||||
// Clear compute shader bindings
|
||||
ID3D11UnorderedAccessView* nullUAV[] = { nullptr };
|
||||
context_->CSSetUnorderedAccessViews(0, 1, nullUAV, nullptr);
|
||||
context_->CSSetShader(nullptr, nullptr, 0);
|
||||
|
||||
// Clear render target
|
||||
float clearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
context_->ClearRenderTargetView(renderTargetView_.Get(), clearColor);
|
||||
|
||||
// Set viewport
|
||||
D3D11_VIEWPORT vp = {};
|
||||
vp.Width = (float)imageWidth_;
|
||||
vp.Height = (float)imageHeight_;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
context_->RSSetViewports(1, &vp);
|
||||
|
||||
// Render to screen
|
||||
context_->OMSetRenderTargets(1, renderTargetView_.GetAddressOf(), nullptr);
|
||||
context_->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
|
||||
context_->VSSetShader(vertexShader_.Get(), nullptr, 0);
|
||||
context_->PSSetShader(pixelShader_.Get(), nullptr, 0);
|
||||
context_->PSSetShaderResources(0, 1, outputSRV_.GetAddressOf());
|
||||
context_->PSSetSamplers(0, 1, samplerState_.GetAddressOf());
|
||||
context_->Draw(3, 0);
|
||||
|
||||
swapChain_->Present(1, 0);
|
||||
|
||||
if (animationEnabled_) {
|
||||
animTime_ += 0.016f;
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK MandelbrotApp::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
MandelbrotApp* pApp = nullptr;
|
||||
|
||||
if (msg == WM_NCCREATE) {
|
||||
CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
|
||||
pApp = reinterpret_cast<MandelbrotApp*>(pCreate->lpCreateParams);
|
||||
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pApp));
|
||||
}
|
||||
else {
|
||||
pApp = reinterpret_cast<MandelbrotApp*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
|
||||
}
|
||||
|
||||
if (pApp) {
|
||||
return pApp->HandleMessage(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
LRESULT MandelbrotApp::HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
|
||||
switch (msg) {
|
||||
case WM_SIZE: {
|
||||
if (swapChain_ && wParam != SIZE_MINIMIZED) {
|
||||
RECT rc;
|
||||
GetClientRect(hwnd, &rc);
|
||||
int width = rc.right - rc.left;
|
||||
int height = rc.bottom - rc.top;
|
||||
|
||||
if (width > 0 && height > 0) {
|
||||
context_->OMSetRenderTargets(0, nullptr, nullptr);
|
||||
renderTargetView_.Reset();
|
||||
|
||||
HRESULT hr = swapChain_->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
|
||||
if (SUCCEEDED(hr)) {
|
||||
ComPtr<ID3D11Texture2D> backBuffer;
|
||||
hr = swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
|
||||
if (SUCCEEDED(hr)) {
|
||||
device_->CreateRenderTargetView(backBuffer.Get(), nullptr, &renderTargetView_);
|
||||
ResizeBuffers(width, height);
|
||||
|
||||
if (context_ && renderTargetView_) {
|
||||
context_->OMSetRenderTargets(1, renderTargetView_.GetAddressOf(), nullptr);
|
||||
|
||||
// Reset viewport
|
||||
D3D11_VIEWPORT vp;
|
||||
vp.Width = (float)width;
|
||||
vp.Height = (float)height;
|
||||
vp.MinDepth = 0.0f;
|
||||
vp.MaxDepth = 1.0f;
|
||||
vp.TopLeftX = 0;
|
||||
vp.TopLeftY = 0;
|
||||
context_->RSSetViewports(1, &vp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
|
||||
case WM_MOUSEWHEEL: {
|
||||
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
|
||||
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
|
||||
ScreenToClient(hwnd, &pt);
|
||||
|
||||
bool shiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
||||
double zoomFactor = shiftPressed ?
|
||||
(delta > 0 ? 0.5 : 2.0) :
|
||||
(delta > 0 ? 0.8 : 1.25);
|
||||
|
||||
Zoom(zoomFactor, pt.x, pt.y);
|
||||
UpdateTitle();
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_RBUTTONDOWN: {
|
||||
SetCapture(hwnd);
|
||||
lastMouseX_ = GET_X_LPARAM(lParam);
|
||||
lastMouseY_ = GET_Y_LPARAM(lParam);
|
||||
rightDragging_ = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_RBUTTONUP: {
|
||||
if (rightDragging_) {
|
||||
int currentX = GET_X_LPARAM(lParam);
|
||||
int currentY = GET_Y_LPARAM(lParam);
|
||||
|
||||
if (abs(currentX - lastMouseX_) < 5 && abs(currentY - lastMouseY_) < 5) {
|
||||
ResetView();
|
||||
}
|
||||
}
|
||||
rightDragging_ = false;
|
||||
ReleaseCapture();
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_LBUTTONDOWN: {
|
||||
SetCapture(hwnd);
|
||||
lastMouseX_ = GET_X_LPARAM(lParam);
|
||||
lastMouseY_ = GET_Y_LPARAM(lParam);
|
||||
dragging_ = true;
|
||||
SetCursor(LoadCursor(nullptr, IDC_SIZEALL));
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_LBUTTONUP: {
|
||||
dragging_ = false;
|
||||
ReleaseCapture();
|
||||
SetCursor(LoadCursor(nullptr, IDC_ARROW));
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_MBUTTONDOWN: {
|
||||
int x = GET_X_LPARAM(lParam);
|
||||
int y = GET_Y_LPARAM(lParam);
|
||||
Zoom(1.0, x, y);
|
||||
UpdateTitle();
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_MOUSEMOVE: {
|
||||
if (dragging_) {
|
||||
int currentX = GET_X_LPARAM(lParam);
|
||||
int currentY = GET_Y_LPARAM(lParam);
|
||||
int dx = currentX - lastMouseX_;
|
||||
int dy = currentY - lastMouseY_;
|
||||
|
||||
if (dx != 0 || dy != 0) {
|
||||
Pan(dx, dy);
|
||||
lastMouseX_ = currentX;
|
||||
lastMouseY_ = currentY;
|
||||
}
|
||||
}
|
||||
else if (rightDragging_) {
|
||||
int currentY = GET_Y_LPARAM(lParam);
|
||||
int dy = currentY - lastMouseY_;
|
||||
|
||||
if (abs(dy) > 2) {
|
||||
AdjustIterations(-dy * 5);
|
||||
lastMouseY_ = currentY;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_KEYDOWN: {
|
||||
keysPressed_[wParam] = true;
|
||||
|
||||
switch (wParam) {
|
||||
case 'H': {
|
||||
const wchar_t* helpText =
|
||||
L"MANDELBROT VIEWER CONTROLS\n\n"
|
||||
L"Mouse:\n"
|
||||
L" Left Drag - Pan view\n"
|
||||
L" Wheel - Zoom in/out\n"
|
||||
L" Shift+Wheel - Fast zoom\n"
|
||||
L" Middle Click - Center on point\n"
|
||||
L" Right Drag - Adjust iterations\n"
|
||||
L" Right Click - Reset view\n\n"
|
||||
L"Keyboard:\n"
|
||||
L" Arrow Keys - Pan view\n"
|
||||
L" Shift+Arrows - Fast pan\n"
|
||||
L" +/= / - - Zoom in/out\n"
|
||||
L" [ / ] - Adjust iterations\n"
|
||||
L" C - Cycle color schemes (5 total)\n"
|
||||
L" A - Toggle animation\n"
|
||||
L" R - Reset view\n"
|
||||
L" S - Save bookmark\n"
|
||||
L" 0-9 - Load bookmark\n"
|
||||
L" F11 - Toggle fullscreen\n"
|
||||
L" ESC - Exit fullscreen\n"
|
||||
L" H - This help";
|
||||
MessageBox(hwnd, helpText, L"Help", MB_OK | MB_ICONINFORMATION);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case 'R':
|
||||
ResetView();
|
||||
return 0;
|
||||
|
||||
case 'C':
|
||||
CycleColorScheme();
|
||||
return 0;
|
||||
|
||||
case 'A':
|
||||
ToggleAnimation();
|
||||
return 0;
|
||||
|
||||
case 'S':
|
||||
SaveBookmark();
|
||||
return 0;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
LoadBookmark(wParam - '0');
|
||||
return 0;
|
||||
|
||||
case VK_OEM_PLUS:
|
||||
case VK_ADD:
|
||||
Zoom(0.8, imageWidth_ / 2, imageHeight_ / 2);
|
||||
UpdateTitle();
|
||||
return 0;
|
||||
|
||||
case VK_OEM_MINUS:
|
||||
case VK_SUBTRACT:
|
||||
Zoom(1.25, imageWidth_ / 2, imageHeight_ / 2);
|
||||
UpdateTitle();
|
||||
return 0;
|
||||
|
||||
case 'D': // [
|
||||
AdjustIterations(-64);
|
||||
return 0;
|
||||
|
||||
case 'I': // ]
|
||||
AdjustIterations(64);
|
||||
return 0;
|
||||
|
||||
case VK_F11: {
|
||||
static bool fullscreen = false;
|
||||
static RECT savedRect;
|
||||
static DWORD savedStyle;
|
||||
|
||||
if (!fullscreen) {
|
||||
GetWindowRect(hwnd, &savedRect);
|
||||
savedStyle = GetWindowLong(hwnd, GWL_STYLE);
|
||||
|
||||
SetWindowLong(hwnd, GWL_STYLE, savedStyle & ~(WS_CAPTION | WS_THICKFRAME));
|
||||
|
||||
MONITORINFO mi = { sizeof(mi) };
|
||||
GetMonitorInfo(MonitorFromWindow(hwnd, MONITOR_DEFAULTTOPRIMARY), &mi);
|
||||
SetWindowPos(hwnd, HWND_TOP,
|
||||
mi.rcMonitor.left, mi.rcMonitor.top,
|
||||
mi.rcMonitor.right - mi.rcMonitor.left,
|
||||
mi.rcMonitor.bottom - mi.rcMonitor.top,
|
||||
SWP_FRAMECHANGED);
|
||||
fullscreen = true;
|
||||
}
|
||||
else {
|
||||
SetWindowLong(hwnd, GWL_STYLE, savedStyle);
|
||||
SetWindowPos(hwnd, nullptr,
|
||||
savedRect.left, savedRect.top,
|
||||
savedRect.right - savedRect.left,
|
||||
savedRect.bottom - savedRect.top,
|
||||
SWP_FRAMECHANGED);
|
||||
fullscreen = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case VK_ESCAPE: {
|
||||
DWORD currentStyle = GetWindowLong(hwnd, GWL_STYLE);
|
||||
if (!(currentStyle & WS_CAPTION)) {
|
||||
SetWindowLong(hwnd, GWL_STYLE, WS_OVERLAPPEDWINDOW);
|
||||
SetWindowPos(hwnd, nullptr, 100, 100, 1280, 720, SWP_FRAMECHANGED);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
case VK_UP:
|
||||
case VK_DOWN: {
|
||||
bool shift = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
|
||||
double speed = shift ? 50 : 10;
|
||||
|
||||
int dx = 0, dy = 0;
|
||||
if (wParam == VK_LEFT) dx = (int)speed;
|
||||
if (wParam == VK_RIGHT) dx = -(int)speed;
|
||||
if (wParam == VK_UP) dy = (int)speed;
|
||||
if (wParam == VK_DOWN) dy = -(int)speed;
|
||||
|
||||
Pan(dx, dy);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_KEYUP:
|
||||
keysPressed_[wParam] = false;
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
void MandelbrotApp::Run() {
|
||||
MSG msg = {};
|
||||
LARGE_INTEGER lastTime;
|
||||
QueryPerformanceCounter(&lastTime);
|
||||
|
||||
double targetFrameTime = 1.0 / 120.0; // 60 FPS
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
|
||||
while (true) {
|
||||
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
|
||||
if (msg.message == WM_QUIT)
|
||||
break;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
else {
|
||||
RenderMandelbrot();
|
||||
|
||||
// Limit frame rate
|
||||
LARGE_INTEGER currentTime;
|
||||
QueryPerformanceCounter(¤tTime);
|
||||
double elapsed = double(currentTime.QuadPart - lastTime.QuadPart) / freq.QuadPart;
|
||||
|
||||
if (elapsed < targetFrameTime) {
|
||||
DWORD sleepMs = DWORD((targetFrameTime - elapsed) * 1000);
|
||||
if (sleepMs > 0) Sleep(sleepMs);
|
||||
}
|
||||
QueryPerformanceCounter(&lastTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
simulations/mandelbrotset/cpp/mandelbrot_app.h
Normal file
91
simulations/mandelbrotset/cpp/mandelbrot_app.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#ifndef MANDELBROT_APP_H
|
||||
#define MANDELBROT_APP_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <d3dcompiler.h>
|
||||
#include <wrl/client.h>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
struct ConstantBuffer {
|
||||
float xmin, xmax, ymin, ymax;
|
||||
int width, height, maxIter;
|
||||
float time;
|
||||
float padding[3];
|
||||
};
|
||||
|
||||
struct Bookmark {
|
||||
double xmin, xmax, ymin, ymax;
|
||||
int maxIter;
|
||||
bool saved = false;
|
||||
};
|
||||
|
||||
class MandelbrotApp {
|
||||
public:
|
||||
MandelbrotApp(HINSTANCE hInstance);
|
||||
~MandelbrotApp();
|
||||
void Run();
|
||||
|
||||
private:
|
||||
void CreateMainWindow();
|
||||
void InitD3D();
|
||||
void CreateComputeShader();
|
||||
void ResizeBuffers(int width, int height);
|
||||
void RenderMandelbrot();
|
||||
void UpdateTitle();
|
||||
|
||||
void Zoom(double factor, int centerX, int centerY);
|
||||
void Pan(int dx, int dy);
|
||||
void ResetView();
|
||||
void AdjustIterations(int delta);
|
||||
void CycleColorScheme();
|
||||
void ToggleAnimation();
|
||||
|
||||
void SaveBookmark();
|
||||
void LoadBookmark(int slot);
|
||||
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
LRESULT HandleMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
HINSTANCE hInstance_;
|
||||
HWND hwnd_;
|
||||
|
||||
ComPtr<ID3D11Device> device_;
|
||||
ComPtr<ID3D11DeviceContext> context_;
|
||||
ComPtr<IDXGISwapChain> swapChain_;
|
||||
ComPtr<ID3D11RenderTargetView> renderTargetView_;
|
||||
ComPtr<ID3D11ComputeShader> computeShader_;
|
||||
ComPtr<ID3D11VertexShader> vertexShader_;
|
||||
ComPtr<ID3D11PixelShader> pixelShader_;
|
||||
ComPtr<ID3D11Buffer> constantBuffer_;
|
||||
ComPtr<ID3D11Texture2D> outputTexture_;
|
||||
ComPtr<ID3D11UnorderedAccessView> outputUAV_;
|
||||
ComPtr<ID3D11ShaderResourceView> outputSRV_;
|
||||
ComPtr<ID3D11SamplerState> samplerState_;
|
||||
|
||||
double xmin_ = -2.5, xmax_ = 1.5;
|
||||
double ymin_ = -1.5, ymax_ = 1.5;
|
||||
int maxIter_ = 256;
|
||||
int colorScheme_ = 0;
|
||||
bool animationEnabled_ = false;
|
||||
float animTime_ = 0.0f;
|
||||
|
||||
int imageWidth_ = 0;
|
||||
int imageHeight_ = 0;
|
||||
int lastMouseX_ = 0;
|
||||
int lastMouseY_ = 0;
|
||||
bool dragging_ = false;
|
||||
bool rightDragging_ = false;
|
||||
std::unordered_map<WPARAM, bool> keysPressed_;
|
||||
std::mutex viewMutex_;
|
||||
|
||||
LARGE_INTEGER perfFreq_;
|
||||
LARGE_INTEGER lastFrameTime_;
|
||||
Bookmark bookmarks_[10];
|
||||
};
|
||||
|
||||
#endif
|
||||
19
simulations/mandelbrotset/generate.py
Normal file
19
simulations/mandelbrotset/generate.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from numba import njit
|
||||
import numpy as np
|
||||
|
||||
@njit
|
||||
def generate_mandelbrot(xmin, xmax, ymin, ymax, width, height, max_iter):
|
||||
img = np.zeros((height, width), dtype=np.uint32)
|
||||
for i in range(height):
|
||||
for j in range(width):
|
||||
x0 = xmin + (xmax - xmin) * j / width
|
||||
y0 = ymin + (ymax - ymin) * i / height
|
||||
x, y = 0.0, 0.0
|
||||
iter = 0
|
||||
while x*x + y*y <= 4.0 and iter < max_iter:
|
||||
xtemp = x*x - y*y + x0
|
||||
y = 2*x*y + y0
|
||||
x = xtemp
|
||||
iter += 1
|
||||
img[i, j] = iter
|
||||
return img
|
||||
95
simulations/mandelbrotset/mandelbrotset.py
Normal file
95
simulations/mandelbrotset/mandelbrotset.py
Normal file
@@ -0,0 +1,95 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from PIL import Image, ImageTk
|
||||
import threading
|
||||
import numpy as np
|
||||
import generate # your mandelbrot_numba / generate_mandelbrot module
|
||||
|
||||
class MandelbrotApp:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("Mandelbrot Viewer")
|
||||
|
||||
self.frame = ttk.Frame(root)
|
||||
self.frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.canvas = tk.Canvas(self.frame, bg="black", scrollregion=(0, 0, 2000, 2000))
|
||||
self.hbar = ttk.Scrollbar(self.frame, orient=tk.HORIZONTAL, command=self.canvas.xview)
|
||||
self.vbar = ttk.Scrollbar(self.frame, orient=tk.VERTICAL, command=self.canvas.yview)
|
||||
self.canvas.config(xscrollcommand=self.hbar.set, yscrollcommand=self.vbar.set)
|
||||
|
||||
self.hbar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||
self.vbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||
self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||
|
||||
# Mandelbrot view window
|
||||
self.xmin, self.xmax = -2.0, 1.0
|
||||
self.ymin, self.ymax = -1.5, 1.5
|
||||
self.zoom_factor = 1.0
|
||||
self.image_on_canvas = None
|
||||
|
||||
self.render_image()
|
||||
|
||||
# Mouse bindings
|
||||
self.canvas.bind("<MouseWheel>", self.zoom)
|
||||
self.canvas.bind("<Button-1>", self.drag_start)
|
||||
self.canvas.bind("<B1-Motion>", self.drag_motion)
|
||||
|
||||
def render_image(self):
|
||||
width, height = 800, 600
|
||||
max_iter = 200
|
||||
|
||||
def render_task():
|
||||
# Generate the Mandelbrot array
|
||||
img_array = generate.generate_mandelbrot(
|
||||
self.xmin, self.xmax, self.ymin, self.ymax,
|
||||
width, height, max_iter
|
||||
)
|
||||
|
||||
# Convert NumPy array to RGB image
|
||||
img_rgb = np.zeros((height, width, 3), dtype=np.uint8)
|
||||
img_rgb[:, :, 0] = img_array % 256
|
||||
img_rgb[:, :, 1] = (img_array * 2) % 256
|
||||
img_rgb[:, :, 2] = (img_array * 3) % 256
|
||||
|
||||
# Convert NumPy array → PIL Image → ImageTk.PhotoImage
|
||||
img = Image.fromarray(img_rgb)
|
||||
self.tk_img = ImageTk.PhotoImage(img)
|
||||
|
||||
# Update canvas
|
||||
self.canvas.delete("all")
|
||||
self.image_on_canvas = self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_img)
|
||||
self.canvas.config(scrollregion=(0, 0, width, height))
|
||||
|
||||
threading.Thread(target=render_task, daemon=True).start()
|
||||
|
||||
def zoom(self, event):
|
||||
zoom_amount = 0.9 if event.delta > 0 else 1.1
|
||||
self.zoom_factor *= zoom_amount
|
||||
center_x = (self.xmin + self.xmax) / 2
|
||||
center_y = (self.ymin + self.ymax) / 2
|
||||
width = (self.xmax - self.xmin) * zoom_amount
|
||||
height = (self.ymax - self.ymin) * zoom_amount
|
||||
self.xmin, self.xmax = center_x - width / 2, center_x + width / 2
|
||||
self.ymin, self.ymax = center_y - height / 2, center_y + height / 2
|
||||
self.render_image()
|
||||
|
||||
def drag_start(self, event):
|
||||
self.last_x, self.last_y = event.x, event.y
|
||||
|
||||
def drag_motion(self, event):
|
||||
dx = event.x - self.last_x
|
||||
dy = event.y - self.last_y
|
||||
x_shift = (self.xmax - self.xmin) * dx / 800
|
||||
y_shift = (self.ymax - self.ymin) * dy / 600
|
||||
self.xmin -= x_shift
|
||||
self.xmax -= x_shift
|
||||
self.ymin += y_shift
|
||||
self.ymax += y_shift
|
||||
self.last_x, self.last_y = event.x, event.y
|
||||
self.render_image()
|
||||
|
||||
if __name__ == "__main__":
|
||||
root = tk.Tk()
|
||||
app = MandelbrotApp(root)
|
||||
root.mainloop()
|
||||
247
simulations/mandelbrotset/mandelbrotsetCPP.cpp
Normal file
247
simulations/mandelbrotset/mandelbrotsetCPP.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
#ifndef _UNICODE
|
||||
#define _UNICODE
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <complex>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
||||
// Window size
|
||||
const int WIDTH = 800;
|
||||
const int HEIGHT = 600;
|
||||
|
||||
// Mandelbrot view
|
||||
double xmin = -2.0, xmax = 1.0;
|
||||
double ymin = -1.5, ymax = 1.5;
|
||||
int max_iter = 500;
|
||||
|
||||
// Pixel buffer
|
||||
std::vector<unsigned char> pixels(WIDTH* HEIGHT * 3);
|
||||
HBITMAP hBitmap = nullptr;
|
||||
std::mutex renderMutex;
|
||||
|
||||
// Track ongoing rendering
|
||||
std::atomic<bool> rendering(false);
|
||||
|
||||
// Low-res rendering factor
|
||||
int previewScale = 4;
|
||||
|
||||
// Forward declarations
|
||||
void generateMandelbrot(int width, int height, std::vector<unsigned char>& buffer);
|
||||
|
||||
// Create or update the bitmap
|
||||
void updateBitmap(HDC hdc)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(renderMutex);
|
||||
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = WIDTH;
|
||||
bmi.bmiHeader.biHeight = -HEIGHT; // top-down
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 24;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
if (!hBitmap)
|
||||
{
|
||||
void* pBits;
|
||||
hBitmap = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, nullptr, 0);
|
||||
}
|
||||
|
||||
SetDIBits(hdc, hBitmap, 0, HEIGHT, pixels.data(), &bmi, DIB_RGB_COLORS);
|
||||
}
|
||||
|
||||
// Multi-threaded block render
|
||||
void renderBlock(int yStart, int yEnd, int width, int height, std::vector<unsigned char>& buffer)
|
||||
{
|
||||
for (int py = yStart; py < yEnd; ++py)
|
||||
{
|
||||
double y0 = ymin + (ymax - ymin) * py / height;
|
||||
for (int px = 0; px < width; ++px)
|
||||
{
|
||||
double x0 = xmin + (xmax - xmin) * px / width;
|
||||
std::complex<double> c(x0, y0), z(0, 0);
|
||||
int iter = 0;
|
||||
while (std::abs(z) <= 2.0 && iter < max_iter) z = z * z + c, iter++;
|
||||
|
||||
int idx = (py * width + px) * 3;
|
||||
buffer[idx + 0] = iter % 256;
|
||||
buffer[idx + 1] = (iter * 2) % 256;
|
||||
buffer[idx + 2] = (iter * 3) % 256;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Multi-threaded Mandelbrot
|
||||
void generateMandelbrot(int width, int height, std::vector<unsigned char>& buffer)
|
||||
{
|
||||
int numThreads = std::thread::hardware_concurrency();
|
||||
std::vector<std::thread> threads;
|
||||
int blockHeight = height / numThreads;
|
||||
|
||||
for (int i = 0; i < numThreads; ++i)
|
||||
{
|
||||
int yStart = i * blockHeight;
|
||||
int yEnd = (i == numThreads - 1) ? height : yStart + blockHeight;
|
||||
threads.emplace_back(renderBlock, yStart, yEnd, width, height, std::ref(buffer));
|
||||
}
|
||||
|
||||
for (auto& t : threads) t.join();
|
||||
}
|
||||
|
||||
// Start rendering in a background thread
|
||||
void startRender(HWND hwnd)
|
||||
{
|
||||
if (rendering) return; // avoid multiple renders
|
||||
rendering = true;
|
||||
|
||||
std::thread([hwnd]()
|
||||
{
|
||||
// 1) Low-res preview
|
||||
int lowW = WIDTH / previewScale, lowH = HEIGHT / previewScale;
|
||||
std::vector<unsigned char> preview(lowW * lowH * 3);
|
||||
generateMandelbrot(lowW, lowH, preview);
|
||||
|
||||
// Upscale preview to full size
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(renderMutex);
|
||||
for (int y = 0;y < HEIGHT;y++)
|
||||
for (int x = 0;x < WIDTH;x++)
|
||||
{
|
||||
int px = x / previewScale;
|
||||
int py = y / previewScale;
|
||||
int idx = (y * WIDTH + x) * 3;
|
||||
int idxSmall = (py * lowW + px) * 3;
|
||||
pixels[idx + 0] = preview[idxSmall + 0];
|
||||
pixels[idx + 1] = preview[idxSmall + 1];
|
||||
pixels[idx + 2] = preview[idxSmall + 2];
|
||||
}
|
||||
}
|
||||
InvalidateRect(hwnd, nullptr, TRUE);
|
||||
|
||||
// 2) Full-res render
|
||||
generateMandelbrot(WIDTH, HEIGHT, pixels);
|
||||
InvalidateRect(hwnd, nullptr, TRUE);
|
||||
|
||||
rendering = false;
|
||||
}).detach();
|
||||
}
|
||||
|
||||
// Dragging
|
||||
int lastX = 0, lastY = 0;
|
||||
bool dragging = false;
|
||||
|
||||
// Window procedure
|
||||
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
|
||||
case WM_PAINT:
|
||||
{
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
updateBitmap(hdc);
|
||||
|
||||
HDC memDC = CreateCompatibleDC(hdc);
|
||||
HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, hBitmap);
|
||||
BitBlt(hdc, 0, 0, WIDTH, HEIGHT, memDC, 0, 0, SRCCOPY);
|
||||
SelectObject(memDC, oldBmp);
|
||||
DeleteDC(memDC);
|
||||
|
||||
EndPaint(hwnd, &ps);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_MOUSEWHEEL:
|
||||
{
|
||||
short delta = GET_WHEEL_DELTA_WPARAM(wParam);
|
||||
double zoomFactor = (delta > 0) ? 0.8 : 1.25;
|
||||
|
||||
double centerX = (xmin + xmax) / 2;
|
||||
double centerY = (ymin + ymax) / 2;
|
||||
double width = (xmax - xmin) * zoomFactor;
|
||||
double height = (ymax - ymin) * zoomFactor;
|
||||
|
||||
xmin = centerX - width / 2;
|
||||
xmax = centerX + width / 2;
|
||||
ymin = centerY - height / 2;
|
||||
ymax = centerY + height / 2;
|
||||
|
||||
startRender(hwnd);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONDOWN:
|
||||
dragging = true;
|
||||
lastX = LOWORD(lParam);
|
||||
lastY = HIWORD(lParam);
|
||||
return 0;
|
||||
|
||||
case WM_LBUTTONUP:
|
||||
dragging = false;
|
||||
return 0;
|
||||
|
||||
case WM_MOUSEMOVE:
|
||||
if (dragging)
|
||||
{
|
||||
int x = LOWORD(lParam);
|
||||
int y = HIWORD(lParam);
|
||||
double dx = (x - lastX) * (xmax - xmin) / WIDTH;
|
||||
double dy = (y - lastY) * (ymax - ymin) / HEIGHT;
|
||||
|
||||
xmin -= dx; xmax -= dx;
|
||||
ymin += dy; ymax += dy;
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
|
||||
startRender(hwnd);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
// Entry point
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int nCmdShow)
|
||||
{
|
||||
const wchar_t CLASS_NAME[] = L"MandelbrotWindow";
|
||||
|
||||
WNDCLASS wc = {};
|
||||
wc.lpfnWndProc = WindowProc;
|
||||
wc.hInstance = hInstance;
|
||||
wc.lpszClassName = CLASS_NAME;
|
||||
RegisterClass(&wc);
|
||||
|
||||
HWND hwnd = CreateWindowEx(
|
||||
0, CLASS_NAME, L"Mandelbrot Explorer", WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, WIDTH, HEIGHT,
|
||||
nullptr, nullptr, hInstance, nullptr
|
||||
);
|
||||
|
||||
ShowWindow(hwnd, nCmdShow);
|
||||
|
||||
// Initial render
|
||||
startRender(hwnd);
|
||||
|
||||
MSG msg = {};
|
||||
while (GetMessage(&msg, nullptr, 0, 0))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user