Im trying to convert the code found here: soft-edged-images-in-gdi, to c++. Its used to create bitmaps with rounded corners and soft edges.
#include <windows.h>
#include <iostream>
#include <exception>
#include <gdiplus.h>
#include <gdipluspath.h>
#include <gdiplusimaging.h>
#include <gdipluspixelformats.h >
#include <wingdi.h>
#pragma comment (lib, "gdiplus.lib")
using namespace Gdiplus;
GraphicsPath* createRoundRect(int x, int y, int width, int height, int radius)
{
GraphicsPath* gp = new GraphicsPath;
if (radius == 0)
gp->AddRectangle(Rect(x, y, width, height));
else
{
gp->AddLine(x + radius, y, x + width - radius, y);
gp->AddArc(x + width - radius, y, radius, radius, 270, 90);
gp->AddLine(x + width, y + radius, x + width, y + height - radius);
gp->AddArc(x + width - radius, y + height - radius, radius, radius, 0, 90);
gp->AddLine(x + width - radius, y + height, x + radius, y + height);
gp->AddArc(x, y + height - radius, radius, radius, 90, 90);
gp->AddLine(x, y + height - radius, x, y + radius);
gp->AddArc(x, y, radius, radius, 180, 90);
gp->CloseFigure();
}
return gp;
}
Brush* createFluffyBrush(GraphicsPath* gp, float* blendPositions, float* blendFactors, INT count, INT* in_out_count)
{
PathGradientBrush* pgb = new PathGradientBrush(gp);
//Blend blend = new Blend();
//blend.Positions = blendPositions;
//blend.Factors = blendFactors;
//pgb->Blend = blend;
/* count:
Type: INT
Integer that specifies the number of elements in the blendFactors array.
This is the same as the number of elements in the blendPositions array.
*/
pgb->SetBlend(blendFactors, blendPositions, count);
pgb->SetCenterColor(Color::White);
// in_out_count:
/*
Type: INT*
Pointer to an integer that, on input, specifies the number of Color objects in the colors array.
If the method succeeds, this parameter, on output, receives the number of surround colors set.
If the method fails, this parameter does not receive a value.
*/
pgb->SetSurroundColors(new Color(Color::Black), in_out_count);
return pgb;
}
enum ChannelARGB
{
Blue = 0,
Green = 1,
Red = 2,
Alpha = 3
};
void transferOneARGBChannelFromOneBitmapToAnother(Bitmap* source, Bitmap* dest, ChannelARGB sourceChannel, ChannelARGB destChannel)
{
if ((source->GetWidth() != dest->GetWidth())
|| (source->GetHeight() != dest->GetHeight())
)
{
//throw ArgumentException();
};
Rect* r = new Rect(0, 0, source->GetWidth(), source->GetHeight());
BitmapData* bdSrc = new BitmapData;
source->LockBits(r, ImageLockMode::ImageLockModeRead, PixelFormat32bppARGB, bdSrc); //PixelFormat(Format32bppArgb)
BitmapData* bdDst = new BitmapData;
dest->LockBits(r, ImageLockMode::ImageLockModeRead, PixelFormat32bppARGB, bdDst);
try
{
byte* bpSrc = (byte*)bdSrc->Scan0;//ToPointer()
byte* bpDst = (byte*)bdDst->Scan0;
bpSrc += (int)sourceChannel;
bpDst += (int)destChannel;
for (int i = r->Height * r->Width; i > 0; i--)
{
*bpDst = *bpSrc;
bpSrc += 4;
bpDst += 4;
}
}
catch (const std::exception&)
{
}
source->UnlockBits(bdSrc);
dest->UnlockBits(bdDst);
}
//////////////////////////////////////
//////////////////////////////////////
//Bitmap* bmpFluffy = new Bitmap(bmpOriginal);
Gdiplus::Bitmap* bmpFluffy = Gdiplus::Bitmap::FromFile(L"picture.png", false);
Rect r(0, 0, bmpFluffy->GetWidth(), bmpFluffy->GetHeight());
Bitmap* bmpMask = new Bitmap(r.Width, r.Height);
Graphics* g = Graphics::FromImage(bmpMask);
GraphicsPath* path = createRoundRect(
r.X, r.Y,
r.Width, r.Height,
min(r.Width, r.Height) / 5);;
int in_out_count = 1;
Brush brush = createFluffyBrush(
path,
new float[] { 0.0f, 0.1f, 1.0f },
new float[] { 0.0f, 0.95f, 1.0f }, 3, &in_out_count);
{
g->FillRectangle(new SolidBrush(Color::Black), r); //Brush to solidbrush
g->SetSmoothingMode(SmoothingMode::SmoothingModeHighQuality);
g->FillPath(&brush, path);
transferOneARGBChannelFromOneBitmapToAnother(
bmpMask,
bmpFluffy,
ChannelARGB::Blue,
ChannelARGB::Alpha);
}
I have no experience with .net, and im not sure if the code has been converted correctly, i wonder if someone could take a look at it.
Current im getting error only at this line: Brush brush = createFluffyBrush(
no suitable constructor exist to convert from "Gdiplus::Brush *" to "Gdiplus::Brush"
createFluffyBrush return a pointer to Brush,
so you should use :
Brush brush = *createFluffyBrush(...)
or (better here ?)
Brush * brush = createFluffyBrush(...)
Avoid using the new operator in modern C++. Gdiplus is kind of old, so you can keep using pointers and new in some places, for example to declare Gdiplus::Bitmap* object. For structures such as Gdiplus::GraphicsPath you can use pass by reference, that's usually the easiest and safest method.
To draw rounded images, get the rounded path, clip the region, then use anti-aliasing to draw a rounded rectangle over that image. Example:
#include <windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
void GetRoundRectPath(Gdiplus::GraphicsPath& path, Gdiplus::Rect rc, int diameter)
{
if (diameter > rc.Width) diameter = rc.Width;
if (diameter > rc.Height) diameter = rc.Height;
int dx = rc.Width - diameter;
int dy = rc.Height - diameter;
Gdiplus::Rect tl(rc.X, rc.Y, diameter, diameter); //top-left
Gdiplus::Rect tr = tl; tr.Offset(dx, 0); //top-right
Gdiplus::Rect br = tl; br.Offset(dx, dy); //bottom-right
Gdiplus::Rect bl = tl; bl.Offset(0, dy); //bottom-left
path.Reset();
path.AddArc(br, 0, 90);
path.AddArc(bl, 90, 90);
path.AddArc(tl, 180, 90);
path.AddArc(tr, 270, 90);
path.CloseFigure();
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static Gdiplus::Image* img = nullptr;
switch (message)
{
case WM_CREATE:
img = Gdiplus::Bitmap::FromFile(L"test.jpg");
if (img->GetLastStatus() != 0)
MessageBox(0, L"no image", 0, 0);
return 0;
case WM_PAINT:
{
if (!img)
break;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
Gdiplus::Graphics g(hdc);
Gdiplus::GraphicsPath path;
Gdiplus::Rect rc{ 10, 10, (int)img->GetWidth(), (int)img->GetHeight() };
GetRoundRectPath(path, rc, 32);
Gdiplus::Region rgn(&path);
g.SetClip(&rgn);
g.DrawImage(img, rc);
rgn.MakeInfinite();
g.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);
Gdiplus::Pen pen(Gdiplus::Color::White, 2.0F);
g.DrawPath(&pen, &path);
EndPaint(hWnd, &ps);
return 0;
}
case WM_DESTROY:
if(img) delete img;
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hinst, HINSTANCE, PSTR, INT)
{
ULONG_PTR token;
Gdiplus::GdiplusStartupInput tmp;
Gdiplus::GdiplusStartup(&token, &tmp, NULL);
WNDCLASS wndClass {0};
wndClass.lpfnWndProc = WndProc;
wndClass.hInstance = hinst;
wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndClass.lpszClassName = L"test";
RegisterClass(&wndClass);
CreateWindow(wndClass.lpszClassName, L"Test", WS_VISIBLE|WS_OVERLAPPEDWINDOW,
100, 100, 800, 600, NULL, NULL, hinst, nullptr);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Gdiplus::GdiplusShutdown(token);
return 0;
}
Related
I'm writing code where a user can pick an image from their computer and rotate it on screen. However, there are two problems that I'm having currently. First, I realized that if the image is rectangular, you can see the old picture before it was rotated behind the new rotated image. Second, when I rotated the image, it seemed to rotate around a certain point on the picture, making the picture go off-screen sometimes. So I wanted to know how I would both keep the old image from showing after I rotated the iamge and also how I would keep the image centered to the screen.
This is my rotate code:
int flip = 1;
void rotateImage(HWND hWnd)
{
HDC hdc = GetDC(hWnd);
Graphics graphic(hdc);
Image* image = Image::FromFile(filePath);
int x = (GetSystemMetrics(SM_CXSCREEN) - image->GetWidth()) / 2;
int y = (GetSystemMetrics(SM_CYSCREEN) - image->GetHeight()) / 2 - 50;
int xx = image->GetWidth();
int yy = image->GetHeight();
if (flip == 1)
image->RotateFlip(Rotate90FlipNone);
else if (flip == 2)
image->RotateFlip(Rotate180FlipNone);
else if (flip == 3)
image->RotateFlip(Rotate270FlipNone);
RECT rc;
HBRUSH hBr;
SetRect(&rc, x, y, x + xx, y + yy);
hBr = CreateSolidBrush(RGB(255, 255, 255));
FillRect(hdc, &rc, hBr);
Status status = graphic.DrawImage(image, x, y);
RECT updateRect = { 0 };
updateRect.left = x;
updateRect.top = y;
updateRect.right = updateRect.left + image->GetWidth();
updateRect.bottom = updateRect.top + image->GetHeight();
flip++;
if (flip > 4) flip = 1;
ReleaseDC(hWnd, hdc);
}
This part of the code
RECT rc;
HBRUSH hBr;
SetRect(&rc, x, y, x + xx, y + yy);
hBr = CreateSolidBrush(RGB(255, 255, 255));
FillRect(hdc, &rc, hBr);
was me trying to solve the issue of the old image appearing after a rotation, but it seems that it cuts out too much of the window and deletes the controls on the window as well.
Some tips:
Each call to GdiplusStartup should be paired with a call to GdiplusShutdown.
Compare window width and height with image's.
Do drawing work in WM_PAINT message
Use window client RECT instead of screen RECT to draw image.
Get image width and heigth after rotate the image.
winAPI Rotating an Image on Screen while Keeping It Centered and the
Background Updated
The following code piece shows mentioned modifications and can achieve this purpose for me. You can refer to.
BOOL fRotate = FALSE;
BOOL fFileOpened = FALSE;
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HRESULT hr;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE:
AddMenus(hWnd);
AddControls(hWnd);
hr = BufferedPaintInit();
break;
case WM_COMMAND:
switch (wParam)
{
case FILE_MENU_EXIT:
// File Menu Exit
DestroyWindow(hWnd);
break;
case FILE_OPEN:
fFileOpened = FALSE;
OpenFileWindow(hWnd);
InvalidateRect(hWnd, NULL, FALSE);
break;
case PIC_EDIT:
if (fFileOpened)
{
displayDialogW(hWnd);
}
else
MessageBoxA(NULL, "Pick an Image File to Edit!", "Error!", MB_ICONINFORMATION | MB_OKCANCEL);
break;
case PIC_ROTATE:
if (fFileOpened)
{
fRotate = TRUE;
InvalidateRect(hWnd, NULL, TRUE);
}
else
MessageBoxA(NULL, "Pick an Image File to Rotate!", "Error!", MB_ICONINFORMATION | MB_OKCANCEL);
break;
}
break;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC screen = BeginPaint(hWnd, &ps);
// 3. Do draw work in WM_PAINT message
if (fRotate)
{
rotateImage(hWnd);
fRotate = FALSE;
}
else if (fFileOpened)
{
HPAINTBUFFER hbuff = BeginBufferedPaint(ps.hdc, &ps.rcPaint, BPBF_COMPATIBLEBITMAP, NULL, &screen);
if (hbuff)
{
RECT rc;
GetClientRect(hWnd, &rc);
FillRect(screen, &rc, GetSysColorBrush(COLOR_WINDOW));
putImage(screen, hWnd);
hr = EndBufferedPaint(hbuff, TRUE);
}
}
EndPaint(hWnd, &ps); } break;
case WM_DESTROY:
BufferedPaintUnInit();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return 0;
}
void OpenFileWindow(HWND hWnd)
{
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (SUCCEEDED(hr))
{
IFileOpenDialog* pFileOpen;
// Create the FileOpenDialog object.
hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL,
IID_IFileOpenDialog, reinterpret_cast<void**>(&pFileOpen));
if (SUCCEEDED(hr))
{
// Show the Open dialog box.
hr = pFileOpen->Show(NULL);
// Get the file name from the dialog box.
if (SUCCEEDED(hr))
{
IShellItem* pItem;
hr = pFileOpen->GetResult(&pItem);
if (SUCCEEDED(hr))
{
PWSTR pszFilePath;
hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath);
// Display the file name to the user.
if (SUCCEEDED(hr))
{
char szBuffer[255];
WideCharToMultiByte(CP_ACP, 0, pszFilePath, -1, szBuffer, sizeof(szBuffer), NULL, NULL);
// JPG/JPEG/PNG
filePath = pszFilePath;
fFileOpened = TRUE;
CoTaskMemFree(pszFilePath);
}
pItem->Release();
}
}
pFileOpen->Release();
}
CoUninitialize();
}
}
void putImage(HDC hdc, HWND hWnd)
{
Graphics graphic(hdc);
Image* image = Image::FromFile(filePath);
// 4. Use window client RECT instead of screen RECT to draw image.
RECT clientRect;
GetClientRect(hWnd, &clientRect);
LONG cx = clientRect.right - clientRect.left;
LONG cy = clientRect.bottom - clientRect.top;
// TODO: 2. compare window width and height with image's before subtract.
int x = (cx - image->GetWidth()) / 2;
int y = (cy - image->GetHeight()) / 2;
Status status = graphic.DrawImage(image, x, y);
#if 1 //For testing purpose and feel free to remove.
// Draw a line at window middle for checking is the image is centered
LONG xMid = cx / 2;
LONG yMid = cy / 2;
Pen pen(Color(255, 0, 0, 255));
graphic.DrawLine(&pen, xMid, 0, xMid, clientRect.bottom);
graphic.DrawLine(&pen, 0, yMid, clientRect.right, yMid);
#endif //
}
int flip = 1;
void rotateImage(HWND hWnd)
{
HDC hdc = GetDC(hWnd);
Graphics graphic(hdc);
Image* image = Image::FromFile(filePath);
// 4. Use window client RECT instead of screen RECT to draw image.
RECT clientRect;
GetClientRect(hWnd, &clientRect);
LONG cx = clientRect.right - clientRect.left;
LONG cy = clientRect.bottom - clientRect.top;
if (flip == 1)
image->RotateFlip(Rotate90FlipNone);
else if (flip == 2)
image->RotateFlip(Rotate180FlipNone);
else if (flip == 3)
image->RotateFlip(Rotate270FlipNone);
// 5. Get image width and heigth after rotate
int xx = image->GetWidth();
int yy = image->GetHeight();
// TODO: 2. compare window width and height with image's before subtract.
int x = (cx - xx) / 2;
int y = (cy - yy) / 2;
RECT rc;
HBRUSH hBr;
SetRect(&rc, x, y, x + xx, y + yy);
hBr = CreateSolidBrush(RGB(255, 255, 255));
FillRect(hdc, &rc, hBr);
Status status = graphic.DrawImage(image, x, y);
// Draw a line at window middle
LONG xMid = cx / 2;
LONG yMid = cy / 2;
Pen pen(Color(255, 0, 0, 255));
graphic.DrawLine(&pen, xMid, 0, xMid, clientRect.bottom);
graphic.DrawLine(&pen, 0, yMid, clientRect.right, yMid);
flip++;
if (flip > 4) flip = 1;
ReleaseDC(hWnd, hdc);
}
Update: Add result of an image not working for OP.
I'm very new to Direct2D programming and have been following a tutorial. I've adapted the example given in the tutorial to a slightly more complicated program that bounces a ball off the boundaries of the window.
My main program (main.cpp):
#include "Graphics.h"
Graphics* graphics;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Exit handler
if (uMsg == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPWSTR cmd, int nCmdShow)
{
WNDCLASSEX windowClass;
SecureZeroMemory(&windowClass, sizeof(WNDCLASSEX));
// Set up window
windowClass.cbSize = sizeof(WNDCLASSEX);
windowClass.hbrBackground = (HBRUSH)COLOR_WINDOW;
windowClass.hInstance = hInstance;
windowClass.lpfnWndProc = WindowProc;
windowClass.lpszClassName = "MainWindow";
windowClass.style = CS_HREDRAW | CS_VREDRAW;
// Register window class and handle
RegisterClassEx(&windowClass);
RECT rect = { 0, 0, 800, 600 };
AdjustWindowRectEx(&rect, WS_OVERLAPPEDWINDOW, false, WS_EX_OVERLAPPEDWINDOW);
HWND windowHandle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "MainWindow", "Test Window", WS_OVERLAPPEDWINDOW, 100, 100,
rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, 0);
if (!windowHandle)
return -1;
graphics = new Graphics();
if (!graphics->Init(windowHandle))
{
delete graphics;
return -1;
}
ShowWindow(windowHandle, nCmdShow);
// Message loop
float x = 51.0, xSpeed = 5.0f, y = 0.0, ySpeed = 5.0f;
MSG message;
message.message = WM_NULL;
while (message.message != WM_QUIT)
{
if (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
DispatchMessage(&message);
else
{
// Ball physics
//xSpeed += 0.6f;
x += xSpeed;
ySpeed += 0.2f;
y += ySpeed;
if (y > rect.bottom - 50)
{
ySpeed = -ySpeed;
}
if (x > rect.right - 50)
{
xSpeed = -xSpeed;
}
else if (x < 50)
{
xSpeed = -xSpeed;
}
// Redraw ball
graphics->beginDraw();
graphics->clearScreen(0.0f, 0.0f, 0.5f);
graphics->drawCircle(x, y, 50.0f, 1.0f, 1.0f, 1.0f, 1.0f);
graphics->endDraw();
}
}
delete graphics;
return 0;
}
My header file (Graphics.h):
#pragma once
#include <Windows.h>
#include <d2d1.h>
class Graphics
{
ID2D1Factory* factory;
ID2D1HwndRenderTarget* renderTarget;
ID2D1SolidColorBrush* brush;
public:
Graphics();
~Graphics();
bool Init(HWND windowHandle);
void beginDraw() { renderTarget->BeginDraw(); }
void endDraw() { renderTarget->EndDraw(); }
void clearScreen(float r, float g, float b);
void drawCircle(float x, float y, float radius, float r, float g, float b, float a);
};
My graphics functions (Graphics.cpp):
#include "Graphics.h"
#define CHECKRES if (res != S_OK) return false
Graphics::Graphics()
{
factory = NULL;
renderTarget = NULL;
brush = NULL;
}
Graphics::~Graphics()
{
if (factory)
factory->Release();
if (renderTarget)
renderTarget->Release();
if (brush)
brush->Release();
}
bool Graphics::Init(HWND windowHandle)
{
HRESULT res = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &factory);
CHECKRES;
RECT rect;
GetClientRect(windowHandle, &rect);
res = factory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(windowHandle, D2D1::SizeU(rect.right, rect.bottom)),
&renderTarget
);
CHECKRES;
res = renderTarget->CreateSolidColorBrush(D2D1::ColorF(0, 0, 0, 0), &brush);
CHECKRES;
return true;
}
void Graphics::clearScreen(float r, float g, float b)
{
renderTarget->Clear(D2D1::ColorF(r, g, b));
}
void Graphics::drawCircle(float x, float y, float radius, float r, float g, float b, float a)
{
brush->SetColor(D2D1::ColorF(r, g, b, a));
renderTarget->DrawEllipse(D2D1::Ellipse(D2D1::Point2F(x, y), radius, radius), brush, 3.0f);
}
While this program does work fine, there is some minor tearing on the ball's bounce. I have seen this question which lead me to this MSDN article. Despite reading the article, I do still not fully understand how to implement double buffering, to hopefully reduce tearing. Can someone provide a concise example and explanation of the ID2D1RenderTarget::CreateCompatibleRenderTarget, as this high level Windows programming is quite different from what I'm used to?
Check article here. ID2D1HwndRenderTarget objects are double buffered by nature and drawing is done to the offscreen buffer first and when drawing ends it will be blitted to the screen.
I'm having a hard time figuring out how to draw a circle in directx. I'm able to draw a triangle, so I imagined that I might be able to draw one by just rotating a triangle in a circle. However, I'm absolutely stumped. I've been scouring the net for hours with no luck. Can anyone help me out? Here's my code:
Main.cpp
#include "Engine.h"
#include "Box2D.h"
#include "Triangle2D.h"
class MyApp : public Engine {
public:
Box2D box2D;
Triangle2D triangle2D;
void OnStartup() override {
box2D.New(10.0f, 10.0f, 100.0f, 100.0f, D3DCOLOR_ARGB(0xff, 0x00, 0x00, 0xff));
triangle2D.New(150.0f, 10.0f, 100.0f, 100.0f, D3DCOLOR_ARGB(0xff, 0xff, 0x00, 0x00));
}
void OnShutdown() override {
box2D.Delete();
triangle2D.Delete();
}
void OnDraw() override {
box2D.Draw();
triangle2D.Draw();
}
void OnMouseDown(int x, int y) {
Debug("%d %d\n", x, y);
}
};
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
MyApp app;
app.Run();
return 0;
}
Engine.cpp
#include "Engine.h"
// Initialize the classes static variables
Engine* Engine::_ENGINE = NULL;
// Returns the current instance of this class
Engine* Engine::GetInstance()
{
return _ENGINE;
}
// Constructor
Engine::Engine()
: _exit(false)
, _window(NULL)
, _directx(NULL)
, _device(NULL)
{
_ENGINE = this;
}
// Deconstructor
Engine::~Engine() {
_ENGINE = NULL;
}
// Run the core main event loop, start to finish
bool Engine::Run() {
// Create the Windows class
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(wc));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_CLASSDC;
wc.lpfnWndProc = _WND_PROC;
wc.hInstance = GetModuleHandle(NULL);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wc.lpszClassName = _GetModuleName();
RegisterClassEx(&wc);
// Adjust the window rect for the flags
RECT rect;
SetRect(&rect, 0, 0, _WIDTH, _HEIGHT);
const DWORD windowFlags = WS_OVERLAPPED | WS_CAPTION | WS_CLIPCHILDREN | WS_SYSMENU | WS_MINIMIZEBOX;
AdjustWindowRectEx(&rect, windowFlags, FALSE, 0);
// Create the window
_window = CreateWindowEx(0, _GetModuleName(), _GetModuleName(), windowFlags, 0, 0, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, GetModuleHandle(NULL), NULL);
if (_window == NULL)
return false;
// Move the window to the center of the screen
RECT system;
SystemParametersInfo(SPI_GETWORKAREA, 0, &system, 0);
MoveWindow(_window, (system.right - system.left) / 2 - (rect.right - rect.left) / 2, (system.bottom - system.top) / 2 - (rect.bottom - rect.top) / 2, rect.right - rect.left, rect.bottom - rect.top, TRUE);
// Startup Direct X
if (_DirectXStartup() == false)
return false;
// Call our startup callback
OnStartup();
// Show the window
ShowWindow(_window, SW_SHOWNORMAL);
// Run the event loop
MSG msg;
ULONGLONG timer = GetTickCount64();
while (!_exit) {
// Handle normal system events
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// Run the timer at the framerate
if (timer + 1000 / _FPS < GetTickCount64()) {
timer = GetTickCount64();
// Clear the buffer
_device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0x00, 0xff, 0x00), 1.0f, NULL);
// Call our draw callback every frame
OnDraw();
// Render to screen
_device->Present(NULL, NULL, NULL, NULL);
}
}
// Hide the window to avoid flicker
ShowWindow(_window, SW_HIDE);
// Call our shutdown callback
OnShutdown();
// Shutdown Direct X
_DirectXShutdown();
// Cleanup and destroy the window
DestroyWindow(_window);
UnregisterClass(_GetModuleName(), GetModuleHandle(NULL));
return true;
}
// Return the DirectX device, needed by other DirectX objects
LPDIRECT3DDEVICE9 Engine::GetDevice() {
return _device;
}
// Return our width
int Engine::GetWidth() {
return _WIDTH;
}
// Return our height
int Engine::GetHeight() {
return _HEIGHT;
}
// Our own custom debug string
void Engine::Debug(const char* message, ...) {
#if _DEBUG
if (message) {
va_list args;
va_start(args, message);
int size = vsnprintf(NULL, 0, message, args);
if (size > 0) {
char* string = new char[size + 1];
vsnprintf(string, size + 1, message, args);
OutputDebugStringA(string);
delete[] string;
}
va_end(args);
}
#endif
}
// This is the Windows callback
LRESULT CALLBACK Engine::_WND_PROC(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
// If we do not have access to an instance of our engine, do nothing
if (Engine::_ENGINE == NULL)
return DefWindowProc(hwnd, uMsg, wParam, lParam);
// Handle system messages
switch (uMsg) {
case WM_LBUTTONDOWN:
Engine::_ENGINE->OnMouseDown(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
return 0;
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
if ((lParam & (1 << 30)) == 0)
Engine::_ENGINE->OnKeyDown((int)wParam);
return 0;
case WM_CHAR:
if ((int)wParam <= 127 && isprint((int)wParam) && (lParam & (1 << 30)) == 0)
Engine::_ENGINE->OnASCIIDown((char)wParam);
return 0;
case WM_CLOSE:
Engine::_ENGINE->_exit = true;
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// Custom function for getting our exe name
WCHAR* Engine::_GetModuleName() {
static WCHAR* TITLE = NULL;
static WCHAR BUFFER[MAX_PATH];
if (TITLE != NULL)
return TITLE;
// Remove the path
GetModuleFileName(NULL, BUFFER, MAX_PATH);
TITLE = wcsrchr(BUFFER, '\\');
if (TITLE == NULL) {
wcscpy(BUFFER, L"Application");
TITLE = BUFFER;
return TITLE;
}
TITLE++;
// Remove the extension
WCHAR* ext = wcsrchr(BUFFER, '.');
if (ext)
*ext = 0;
return TITLE;
}
// Startup DirectX here
bool Engine::_DirectXStartup() {
// Startup Direct X
_directx = Direct3DCreate9(D3D_SDK_VERSION);
if (_directx == NULL)
return false;
// Create a Direct X device
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.hDeviceWindow = _window;
d3dpp.BackBufferWidth = _WIDTH;
d3dpp.BackBufferHeight = _HEIGHT;
d3dpp.BackBufferCount = 1;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24X8;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
HRESULT result = _directx->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _window, D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &_device);
if (FAILED(result))
result = _directx->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, _window, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &_device);
if (FAILED(result))
return false;
return true;
}
// Shutdown DirectX here
bool Engine::_DirectXShutdown() {
if (_device) {
_device->Release();
_device = NULL;
}
if (_directx) {
_directx->Release();
_directx = NULL;
}
return true;
}
Engine.h
#ifndef _ENGINE_H_
#define _ENGINE_H_
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <windowsx.h>
#include <stdio.h>
#include <d3d9.h>
#include <d3dx9.h>
class Engine {
public:
// Return the current singleton of this Engine class
static Engine* GetInstance();
// Constructor and deconstructor
Engine();
virtual ~Engine();
// Run the Engine class
bool Run();
// Return useful information about the class
LPDIRECT3DDEVICE9 GetDevice();
int GetWidth();
int GetHeight();
// Use for debug output
static void Debug(const char* message, ...);
// Virtual callbacks
virtual void OnStartup() {}
virtual void OnShutdown() {}
virtual void OnMouseDown(int x, int y) {}
virtual void OnKeyDown(int key) {}
virtual void OnASCIIDown(char key) {}
virtual void OnDraw() {}
private:
static Engine* _ENGINE;
static const int _WIDTH = 854;
static const int _HEIGHT = 480;
static const int _FPS = 60;
bool _exit;
HWND _window;
LPDIRECT3D9 _directx;
LPDIRECT3DDEVICE9 _device;
static LRESULT CALLBACK _WND_PROC(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
WCHAR* _GetModuleName();
bool _DirectXStartup();
bool _DirectXShutdown();
};
#endif // _ENGINE_H_
Triangle2D.h
#ifndef _TRIANGLE_2D_H_
#define _TRIANGLE_2D_H_
#include "Engine.h"
class Triangle2D {
public:
Triangle2D();
bool New(FLOAT x, FLOAT y, FLOAT width, FLOAT height, DWORD color);
void Delete();
void Draw();
void Draw(FLOAT x, FLOAT y, FLOAT width, FLOAT height, DWORD color);
private:
struct CustomVertex {
FLOAT x, y, z, w;
DWORD color;
};
static const DWORD _FVF = D3DFVF_XYZRHW | D3DFVF_DIFFUSE;
LPDIRECT3DVERTEXBUFFER9 _vb;
};
#endif // _BOX_2D_H_
Triangle2D.cpp
#include "Triangle2D.h"
Triangle2D::Triangle2D()
: _vb(NULL)
{
}
bool Triangle2D::New(FLOAT x, FLOAT y, FLOAT width, FLOAT height, DWORD color) {
if (FAILED(Engine::GetInstance()->GetDevice()->CreateVertexBuffer(sizeof(CustomVertex)* 4, D3DUSAGE_WRITEONLY, _FVF, D3DPOOL_DEFAULT, &_vb, NULL)))
return false;
CustomVertex* vertices;
_vb->Lock(0, 0, (void**)&vertices, 0);
vertices[0].x = x;
vertices[0].y = y;
vertices[0].z = 0.0f;
vertices[0].w = 1.0f;
vertices[0].color = color;
vertices[1].x = x + width;
vertices[1].y = y;
vertices[1].z = 0.0f;
vertices[1].w = 1.0f;
vertices[1].color = color;
vertices[2].x = x;
vertices[2].y = y + height;
vertices[2].z = 0.0f;
vertices[2].w = 1.0f;
vertices[2].color = color;
_vb->Unlock();
return true;
}
void Triangle2D::Delete(){
if (_vb) {
_vb->Release();
_vb = NULL;
}
}
void Triangle2D::Draw() {
Engine::GetInstance()->GetDevice()->BeginScene();
Engine::GetInstance()->GetDevice()->SetFVF(_FVF);
Engine::GetInstance()->GetDevice()->SetStreamSource(0, _vb, 0, sizeof(CustomVertex));
Engine::GetInstance()->GetDevice()->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
Engine::GetInstance()->GetDevice()->EndScene();
}
void Triangle2D::Draw(FLOAT x, FLOAT y, FLOAT width, FLOAT height, DWORD color) {
CustomVertex* vertices;
_vb->Lock(0, 0, (void**)&vertices, 0);
vertices[0].x = x;
vertices[0].y = y;
vertices[0].z = 0.0f;
vertices[0].w = 1.0f;
vertices[0].color = color;
vertices[1].x = x + width;
vertices[1].y = y;
vertices[1].z = 0.0f;
vertices[1].w = 1.0f;
vertices[1].color = color;
vertices[2].x = x;
vertices[2].y = y + height;
vertices[2].z = 0.0f;
vertices[2].w = 1.0f;
vertices[2].color = color;
_vb->Unlock();
Draw();
}
Direct3D does not draw "circles". It only natively draws three fundamental primitives: points, lines, and triangles. There are lots of options for how it draws those things, but that's all it can draw natively. The same is true of OpenGL (which can sometimes draw 'quads' as well, but you can always draw a quad with two triangles).
Typically drawing circles and other smooth objects is best done through "vector graphics" libraries. These can render high-quality approximations of smooth objects like circles at the correct resolution. This is what the legacy GDI library did, and what Direct2D can do. You can code your own approximation for a circle, but you probably won't do as well as Direct2D. These libraries ultimately generate points, lines, and triangles for the actual drawing operations which are performed by Direct3D.
For this reason, if you are doing 'presentation' graphics, you should look at using Direct2D instead of Direct3D. Most games actually never draw true circles. They draw 2D sprites as two textured triangles with Direct3D using an image of a circle drawn on it by an artist in something like Photoshop or Paint.
If you are insistent on using Direct3D, see the DirectX Tool Kit for Direct3D 11 DebugDraw helper that can draw 'rings' (i.e. line-segment circles in 3D space). This should give you an idea for how this is done. This code always uses 32 segments to form the ring, but a 'vector graphics' library would determine how many segments to break it down to based on exactly how many pixels it's going to cover on the screen:
void XM_CALLCONV DX::DrawRing(PrimitiveBatch<VertexPositionColor>* batch,
FXMVECTOR origin,
FXMVECTOR majorAxis,
FXMVECTOR minorAxis,
GXMVECTOR color)
{
static const size_t c_ringSegments = 32;
VertexPositionColor verts[c_ringSegments + 1];
FLOAT fAngleDelta = XM_2PI / float(c_ringSegments);
// Instead of calling cos/sin for each segment we calculate
// the sign of the angle delta and then incrementally calculate sin
// and cosine from then on.
XMVECTOR cosDelta = XMVectorReplicate(cosf(fAngleDelta));
XMVECTOR sinDelta = XMVectorReplicate(sinf(fAngleDelta));
XMVECTOR incrementalSin = XMVectorZero();
static const XMVECTORF32 s_initialCos =
{
1.f, 1.f, 1.f, 1.f
};
XMVECTOR incrementalCos = s_initialCos.v;
for (size_t i = 0; i < c_ringSegments; i++)
{
XMVECTOR pos = XMVectorMultiplyAdd(majorAxis, incrementalCos, origin);
pos = XMVectorMultiplyAdd(minorAxis, incrementalSin, pos);
XMStoreFloat3(&verts[i].position, pos);
XMStoreFloat4(&verts[i].color, color);
// Standard formula to rotate a vector.
XMVECTOR newCos = incrementalCos * cosDelta - incrementalSin * sinDelta;
XMVECTOR newSin = incrementalCos * sinDelta + incrementalSin * cosDelta;
incrementalCos = newCos;
incrementalSin = newSin;
}
verts[c_ringSegments] = verts[0];
batch->Draw(D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP, verts, c_ringSegments + 1);
}
Direct3D 9 is legacy and unless you are specifically targeting Windows XP, you should be using Direct3D 11 or perhaps Direct2D if you are really doing 2D presentation graphics.
I'm trying to draw some text in OpenGL while the program draw a cube or any Opengl native so, when I try to put the text on the screen it flashes very fast and I don't know why, I tried to change the Sleep value and nothing...
The code is below; here is a GIF showing the problem.
The green background is the cube, the camera is very close to the background, you can move back with the NUM_2.
#include <windows.h>
#include <iostream>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <sstream>
#include "default.h"
using namespace std;
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
DWORD WINAPI WorkLoop(LPVOID PARAMS);
void keyScan (MSG msg, Camera cam);
HDC hDC;
HGLRC hRC;
HWND hwnd;
RECT WBounds;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
WNDCLASSEX wcex;
MSG msg;
BOOL bQuit = FALSE;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_OWNDC;
wcex.lpfnWndProc = WindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = "GLSample";
wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);;
Screen ();
if (!RegisterClassEx(&wcex))
return 0;
hwnd = CreateWindowEx(0,
"GLSample",
"OpenGL Testing",
WS_OVERLAPPEDWINDOW,
Scr.sx/2-630,
Scr.sy/2-450,
1260,
900,
NULL,
NULL,
hInstance,
NULL);
GetClientRect(hwnd, &WBounds);
ShowWindow(hwnd, nCmdShow);
EnableOpenGL(hwnd, &hDC, &hRC); ///ENABLE OPENGL
Camera cam = Camera (0, 0, -1);
CreateThread(0, 0x1000, &WorkLoop, 0, 0, 0);
while (!bQuit)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT) bQuit = TRUE;
else
{
keyScan (msg, cam);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else
{
renderSimulation (cam);
SwapBuffers (hDC);
}
Sleep(1);
}
DisableOpenGL(hwnd, hDC, hRC);
DestroyWindow(hwnd);
return msg.wParam;
}
DWORD WINAPI WorkLoop(LPVOID PARAMS)
{
while (true)
{
InvalidateRect(hwnd, &WBounds, true);
Sleep(33);
}
ExitThread(0);
}
float x = 0.0f, y = 0.0f, z = 0.0f;
float rx = 0.0f, ry = 0.0f, rz = 0.0f;
char* textas = "test";
void keyScan (MSG p, Camera cam)
{
if (p.message == WM_KEYDOWN)
{
if (p.wParam == ARROW_RIGHT) {x += 0.1; cam.SetCameraPosition (x, y, z);}
else if (p.wParam == ARROW_LEFT) {x -= 0.1; cam.SetCameraPosition (x, y, z);}
else if (p.wParam == ARROW_UP) {y += 0.1; cam.SetCameraPosition (x, y, z);}
else if (p.wParam == ARROW_DOWN) {y -= 0.1; cam.SetCameraPosition (x, y, z);}
else if (p.wParam == NUM_8) {z += 0.1; cam.SetCameraPosition (x, y, z);}
else if (p.wParam == NUM_2) {z -= 0.1; cam.SetCameraPosition (x, y, z);}
else if (p.wParam == L) SetFullScreen (p.hwnd, hDC, hRC);
else if (p.wParam == K) textas = "cambiado";
}
}
HFONT Font = CreateFont(40, 0, 0, 0,FW_NORMAL, FALSE, FALSE, FALSE, DEFAULT_CHARSET,OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS,ANTIALIASED_QUALITY, FF_MODERN, TEXT("Arial"));
HPEN BoxPen = CreatePen(PS_SOLID, 1, RGB(0, 255, 0));
HPEN OutlinePen = CreatePen(PS_SOLID, 3, RGB(0, 0, 0));
HPEN CHPen = CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
HBRUSH CHBrush = CreateSolidBrush(RGB(0, 255, 0));
void drawtext(HDC hdc, int x, int y, const char * text)
{
SetBkMode (hdc, TRANSPARENT);
SetTextColor(hdc, RGB(0, 255, 0));
SetBkColor(hdc, RGB(255, 255, 255));
TextOutA(hdc, x, y, text, strlen(text));
}
/*void Draw(HDC hdc, int x, int y, float dist)
{
int width = 20000 / dist;
int height = 45000 / dist;
SelectObject(hdc, OutlinePen);
SelectObject(hdc, WHITE_BRUSH);
Rectangle(hdc, x - (width / 2), y - height, x + (width / 2), y);
SelectObject(hdc, BoxPen);
Rectangle(hdc, x - (width / 2), y - height, x + (width / 2), y);
SetTextAlign(hdc, TA_CENTER | TA_NOUPDATECP);
std::stringstream ss2;
ss2 << "Dist: " << dist << " m";
drawtext(hdc, x, y + 90, ss2.str().c_str());
}*/
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
int win_width = WBounds.right - WBounds.left;
int win_height = WBounds.bottom + WBounds.left;
PAINTSTRUCT ps;
HDC Memhdc;
HDC hdc;
HBITMAP Membitmap;
hdc = BeginPaint(hwnd, &ps);
Memhdc = CreateCompatibleDC (hdc);
Membitmap = CreateCompatibleBitmap (hdc, win_width, win_height);
SelectObject (Memhdc, Membitmap);
//FillRect (Memhdc, &WBounds, WHITE_BRUSH);
SelectObject (Memhdc, Font);
SetTextAlign (Memhdc, TA_LEFT | TA_NOUPDATECP);
drawtext(Memhdc, 100, 100, textas);
//Draw (Memhdc, 20, 50, 90.0);
/*SelectObject(Memhdc, CHPen);
SelectObject(Memhdc, CHBrush);*/
BitBlt (hdc, 0, 0, win_width, win_height, Memhdc, 0, 0, SRCCOPY);
DeleteObject(Membitmap);
DeleteDC(Memhdc);
DeleteDC(hdc);
EndPaint(hwnd, &ps);
ValidateRect(hwnd, &WBounds);
}
case WM_KEYDOWN:
{
switch (wParam)
{
case VK_ESCAPE:
PostQuitMessage(0);
}
}
case WM_ERASEBKGND:
return 1;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
Two problems with your program:
Setting a background brush for a OpenGL window will make the OS visibly clear the window with the brush before sending WM_PAINT (upon which you overdraw with OpenGL). By using InvalidateRect you're triggering this erase-with-background before WM_PAINT
Double buffered OpenGL pixelformats and drawing with the GDI don't go well together. If you want to draw text you'll have to do it differently. For example drawing to a DIBSECTION DC and then drawing that bitmap per using a textured quad. Or using a font rasterizer that addresses OpenGL (FTGL, Glyphy or the likes).
I'm currently writting a 3D visualization tool for a scientific application and I am facing a performance problem.
I have a relatively large grid (~ 1000 rows x 1000 columns) and for each point of this grid I have a physical value that I want to represent as height (for instance: temperature).
Here is an example of what I am trying to draw with white gaussian noise:
http://i.stack.imgur.com/KM23m.jpg
I am using DirectX 9 to draw my scene. I basiclaly draw a bunch of triangles with the (X,Y) coordinate being a point on the grid, and the Z coordinate being the physical measurement at that point.
Here are the operations that I do on each frame:
I create a vertex buffer (CreateVertexBuffer()) and an index buffer (CreateIndexBuffer()) (the data changes on each frame, but the size of the data does not)
I lock them
I fill the two buffers properly, including assigning a color depending on the value (high value are red, low value are blue)
I unlock them
I set the stream source (SetStreamSource()), set the indices (SetIndices()) and draw the triangles (as a triangle strip)
I release the two buffers
My problem is that the frame rate is not as high as expected.
I achieve ~30fps for ~2 millions triangles drawn on a Nvidia GTX 770 (& Intel Core i7 4770k if that matters) when I want to have at least 60fps.
Is there a better way to do what I am doing or my problem is that the number of triangle is too large ?
Will I get better performance if I use DirectX 11 ?
Thank you for your help.
Edit here is a stand-alone simplified code:
#include <windows.h>
#define _USE_MATH_DEFINES
#include <math.h>
#include <windowsx.h>
#include <d3d9.h>
#include <vector>
#include <random>
#include <fcntl.h>
#include <io.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
#define COLORMAPSIZE 256
#pragma comment (lib, "d3d9.lib")
#define DEG2RAD(x) (x* (float)M_PI/180.0f)
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE)
std::default_random_engine randGenerator;
// global declarations
LPDIRECT3D9 d3d;
LPDIRECT3DDEVICE9 d3ddev;
const int Nrows = 1000, Ncols = 2000;
float indexAz=DEG2RAD(90), indexEl = DEG2RAD(60), distance=80;
const float dataAmplitude = 5.f;
std::vector<float> dataBuffer;
typedef struct D3DXVECTOR3 : public D3DVECTOR
{
public:
FLOAT x,y,z;
D3DXVECTOR3() {};
D3DXVECTOR3( FLOAT xx, FLOAT yy, FLOAT zz ) : x(xx), y(yy), z(zz) {};
} D3DXVECTOR3, *LPD3DXVECTOR3;
typedef struct {
float x, y, z;
D3DCOLOR color;
} Vertex;
void initD3D(HWND hWnd);
void resetD3D(HWND hWnd);
void render_frame(void);
void cleanD3D(void);
void draw_graphics(void);
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void CreateConsole() {
CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
int consoleHandleR, consoleHandleW ;
long stdioHandle;
FILE *fptr;
AllocConsole();
std::wstring strW = L"Dev Console";
SetConsoleTitleW( strW.c_str() );
EnableMenuItem(GetSystemMenu(GetConsoleWindow(), FALSE), SC_CLOSE , MF_GRAYED);
DrawMenuBar(GetConsoleWindow());
GetConsoleScreenBufferInfo( GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo );
stdioHandle = (long)GetStdHandle( STD_INPUT_HANDLE );
consoleHandleR = _open_osfhandle( stdioHandle, _O_TEXT );
fptr = _fdopen( consoleHandleR, "r" );
*stdin = *fptr;
setvbuf( stdin, NULL, _IONBF, 0 );
stdioHandle = (long) GetStdHandle( STD_OUTPUT_HANDLE );
consoleHandleW = _open_osfhandle( stdioHandle, _O_TEXT );
fptr = _fdopen( consoleHandleW, "w" );
*stdout = *fptr;
setvbuf( stdout, NULL, _IONBF, 0 );
stdioHandle = (long)GetStdHandle( STD_ERROR_HANDLE );
*stderr = *fptr;
setvbuf( stderr, NULL, _IONBF, 0 );
}
// Generate a random number following a uniform distribution
double rand(const double inf=0, const double sup=1) {
std::uniform_real_distribution<double> distribution(inf,sup);
return distribution(randGenerator);
}
// Update the buffer with new data
void UpdateDataBuffer()
{
static bool firstCall = true;
if (firstCall) //fill the whole buffer
{
for(unsigned k = 0 ; k < Nrows*Ncols ; k++)
dataBuffer[k] = (float)rand(0,dataAmplitude);
firstCall = false;
} else { // remove the first column, shift the whole buffer and update the last column
memmove(&dataBuffer[0], &dataBuffer[Nrows], (Ncols-1)*Nrows*sizeof(float));
for(unsigned k= Nrows*(Ncols-1) ; k < Nrows*Ncols ; k++)
dataBuffer[k] = (float)rand(0,dataAmplitude);
}
}
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
CreateConsole();
randGenerator.seed( GetTickCount() );
dataBuffer.resize(Nrows * Ncols);
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.lpszClassName = "WindowClass";
RegisterClassEx(&wc);
hWnd = CreateWindowEx(NULL, "WindowClass", "Our Direct3D Program",
WS_OVERLAPPEDWINDOW, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nCmdShow);
initD3D(hWnd);
MSG msg;
LARGE_INTEGER frequency;
LARGE_INTEGER t1, t2;
float fps = 0.f;
float NFramAvg = 1.0f/10.0f, totalElapsedTime = 0.0f;
QueryPerformanceFrequency(&frequency);
while(TRUE)
{
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if(msg.message == WM_QUIT)
break;
UpdateDataBuffer();
QueryPerformanceCounter(&t1);
render_frame();
QueryPerformanceCounter(&t2);
fps = fps - NFramAvg*fps + NFramAvg* frequency.QuadPart / (t2.QuadPart - t1.QuadPart);
totalElapsedTime += (t2.QuadPart - t1.QuadPart)*1000.0f / frequency.QuadPart;;
if (totalElapsedTime > 1000) {
printf("FPS = %g\n", fps);
totalElapsedTime = 0.0;
}
}
cleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static int clickX, clickY;
static bool down = false;
switch(message)
{
case WM_LBUTTONDOWN:
if (!down)
{
clickX = GET_X_LPARAM(lParam);
clickY = GET_Y_LPARAM(lParam);
}
down = true;
break;
case WM_LBUTTONUP:
down = false;
break;
case WM_MOUSEMOVE:
if (down)
{
int dx = GET_X_LPARAM(lParam) - clickX;
int dy = GET_Y_LPARAM(lParam) - clickY;
indexAz += dx*DEG2RAD(0.5f);
if (indexEl + dy*DEG2RAD(0.5f) < M_PI_2 && indexEl + dy*DEG2RAD(0.5f) > -M_PI_2)
indexEl += dy*DEG2RAD(0.5f);
clickX += dx;
clickY += dy;
}
break;
case WM_MOUSEWHEEL:
{
int zDelta = GET_WHEEL_DELTA_WPARAM(wParam); //scroll power
distance -= 2*zDelta/120.f;
distance = max(1.0f, distance);
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
case WM_SIZE:
if (d3ddev)
resetD3D(hWnd);
break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
d3d = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
RECT rect;
GetClientRect(hWnd, &rect);
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = rect.right - rect.left;
d3dpp.BackBufferHeight = rect.bottom -rect.top;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
d3d->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp,
&d3ddev);
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // turn off culling
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE); // turn on the z-buffer
}
void resetD3D(HWND hWnd)
{
D3DPRESENT_PARAMETERS d3dpp;
RECT rect;
GetClientRect(hWnd, &rect);
ZeroMemory(&d3dpp, sizeof(d3dpp));
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hWnd;
d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
d3dpp.BackBufferWidth = rect.right - rect.left;
d3dpp.BackBufferHeight = rect.bottom -rect.top;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
if (d3dpp.BackBufferWidth && d3dpp.BackBufferHeight)
{
d3ddev->Reset(&d3dpp);
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
InvalidateRect(hWnd, NULL, FALSE);
}
d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); // turn off culling
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE); // turn on the z-buffer
}
D3DXVECTOR3 *D3DVec3Subtract(D3DXVECTOR3 *pOut, const D3DXVECTOR3 *pV1,const D3DXVECTOR3 *pV2)
{
pOut->x = pV1->x - pV2->x;
pOut->y = pV1->y - pV2->y;
pOut->z = pV1->z - pV2->z;
return pOut;
}
D3DXVECTOR3* D3DVec3Normalize(D3DXVECTOR3 *pOut,const D3DXVECTOR3 *pV)
{
FLOAT norm = sqrt( pV->x * pV->x + pV->y * pV->y + pV->z * pV->z );
pOut->x = pV->x / norm;
pOut->y = pV->y / norm;
pOut->z = pV->z / norm;
return pOut;
}
D3DXVECTOR3* D3DVec3Cross(D3DXVECTOR3 *pOut,const D3DXVECTOR3 *pV1,const D3DXVECTOR3 *pV2)
{
pOut->x = pV1->y*pV2->z - pV1->z*pV2->y;
pOut->y = pV1->z*pV2->x - pV1->x*pV2->z;
pOut->z = pV1->x*pV2->y - pV1->y*pV2->x;
return pOut;
}
FLOAT D3DVec3Dot(const D3DXVECTOR3 *pV1,const D3DXVECTOR3 *pV2)
{
return pV1->x * pV2->x + pV1->y * pV2->y + pV1->z * pV2->z;
}
D3DMATRIX* D3DMatrixLookAtLH(D3DMATRIX *pOut,const D3DXVECTOR3 *pEye,const D3DXVECTOR3 *pAt,const D3DXVECTOR3 *pUp)
{
D3DXVECTOR3 right, rightn, up, upn, vec, vec2;
D3DVec3Subtract(&vec2, pAt, pEye);
D3DVec3Normalize(&vec, &vec2);
D3DVec3Cross(&right, pUp, &vec);
D3DVec3Cross(&up, &vec, &right);
D3DVec3Normalize(&rightn, &right);
D3DVec3Normalize(&upn, &up);
pOut->m[0][0] = rightn.x;
pOut->m[1][0] = rightn.y;
pOut->m[2][0] = rightn.z;
pOut->m[3][0] = -D3DVec3Dot(&rightn,pEye);
pOut->m[0][1] = upn.x;
pOut->m[1][1] = upn.y;
pOut->m[2][1] = upn.z;
pOut->m[3][1] = -D3DVec3Dot(&upn, pEye);
pOut->m[0][2] = vec.x;
pOut->m[1][2] = vec.y;
pOut->m[2][2] = vec.z;
pOut->m[3][2] = -D3DVec3Dot(&vec, pEye);
pOut->m[0][3] = 0.0f;
pOut->m[1][3] = 0.0f;
pOut->m[2][3] = 0.0f;
pOut->m[3][3] = 1.0f;
return pOut;
}
D3DMATRIX* D3DXMatrixPerspectiveFovLH(D3DMATRIX *pOut, const FLOAT fovy, const FLOAT Aspect, const FLOAT zn, const FLOAT zf)
{
FLOAT yScale = tanf((float)M_PI_2 - fovy/2);
FLOAT xScale = yScale /Aspect;
memset(pOut,0, sizeof(*pOut));
pOut->_11 = xScale;
pOut->_22 = yScale;
pOut->_33 = zf/(zf-zn);
pOut->_34 = 1;
pOut->_43 = -zn*zf/(zf-zn);
return pOut;
}
long GetColor(const float x, const float inf, const float sup)
{
BYTE c =(BYTE)( 255 * (x-inf)/(sup-inf) );
return D3DCOLOR_XRGB(c,c,c);
}
// this is the function that puts the 3D models into video RAM
void draw_graphics(void)
{
static long colorTab[COLORMAPSIZE] = {0,};
static std::vector<Vertex> tmp;
static LPDIRECT3DVERTEXBUFFER9 v_buffer = NULL;
static LPDIRECT3DINDEXBUFFER9 i_buffer = NULL;
static unsigned NVertex = 0;
// Create empty IDirect3DTexture9
const unsigned MN = Nrows*Ncols;
unsigned k=0;
if (MN > tmp.size())
tmp.resize( MN );
if (colorTab[0] == 0) // if colortab empty, fill it
{
for(int i=0 ; i < COLORMAPSIZE ;i++)
colorTab[i] = GetColor((float)i, (float)0, (float)(COLORMAPSIZE-1));
}
if (!v_buffer)
d3ddev->CreateVertexBuffer(MN*sizeof(Vertex), 0,D3DFVF_XYZ | D3DFVF_DIFFUSE,D3DPOOL_MANAGED,&v_buffer,NULL);
float factor = (COLORMAPSIZE-1.0f)/dataAmplitude;
for (k=0 ; k < MN ; k++)
{
if (dataBuffer[k] >= dataAmplitude)
tmp[k].color = colorTab[COLORMAPSIZE-1];
else if (dataBuffer[k] <= 0)
tmp[k].color = colorTab[0];
else
tmp[k].color = colorTab[(int)( ( dataBuffer[k])*factor )];
}
float M_2 = Nrows/2.0f, N_2 = Ncols/2.0f;
k=0;
for (unsigned n=0 ; n < Ncols ; n++)
{
for (unsigned m=0 ; m < Nrows ; m++, k++)
{
tmp[k].x = M_2 - m;
tmp[k].z = n - N_2;
tmp[k].y = dataBuffer[k];
}
}
Vertex* pVoid;
v_buffer->Lock(0, 0, (void**)&pVoid, 0);
memcpy(pVoid, &tmp[0], MN*sizeof(Vertex));
v_buffer->Unlock();
if (!i_buffer)
d3ddev->CreateIndexBuffer(3*2*(Nrows-1)*(Ncols-1)*sizeof(DWORD), 0, D3DFMT_INDEX32, D3DPOOL_MANAGED,&i_buffer,NULL);
DWORD *indices;
i_buffer->Lock(0, 0, (void**)&indices, 0);
k=0;
for (unsigned n=0 ; n < Ncols-1 ; n++)
{
if (n!=0)
indices[k++] = n*Nrows;
for (unsigned m=0 ; m < Nrows-1 ; m++)
{
indices[k++] = m + n*Nrows;
indices[k++] = m + (n+1)*Nrows;
}
indices[k++] = Nrows-2 + (n+1)*Nrows;
}
NVertex = k;
i_buffer->Unlock();
d3ddev->SetStreamSource(0, v_buffer, 0, sizeof(Vertex));
d3ddev->SetIndices(i_buffer);
d3ddev->DrawIndexedPrimitive(D3DPT_TRIANGLESTRIP, 0, 0, MN, 0, NVertex-2);
//printf("%d triangle drawn\n", NVertex-2);
//i_buffer->Release();
//v_buffer->Release();
}
// this is the function used to render a single frame
void render_frame(void)
{
d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->BeginScene();
d3ddev->SetFVF(CUSTOMFVF);
// set the view transform
D3DMATRIX matView; // the view transform matrix
float R = distance*25;
D3DMatrixLookAtLH(&matView,
&D3DXVECTOR3 (R*sin(indexAz)*cos(indexEl), R*sin(indexEl),R*cos(indexAz)*cos(indexEl)), // the camera position
&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at position
&D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); // the up direction
d3ddev->SetTransform(D3DTS_VIEW, &matView); // set the view transform to matView
// set the projection transform
D3DMATRIX matProjection; // the projection transform matrix
D3DXMatrixPerspectiveFovLH(&matProjection,
DEG2RAD(45), // the horizontal field of view
(FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
0.001f, // the near view-plane
100000.f); // the far view-plane
d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection
indexAz += DEG2RAD(0.1f);
draw_graphics();
d3ddev->EndScene();
d3ddev->Present(NULL, NULL, NULL, NULL);
}
// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
d3ddev->Release();
d3d->Release();
}
Well, because you say you're IO bound, the best thing would be to reduce the amount of data you send to the GPU every update.
If you're targeting SM 3.0 or higher you can use vertex texture fetching to sample the height from a texture that you update every frame. This is a lot easier on the input assembler and on BW.
To do this most easily, you might want to change your vertex data, instead of supplying a float3 with xyz, you might want to supply a uint2 with just xz, this way you can directly fetch from the texture without doing a float to int conversion in your shader.
as a side note, there is very little reason for you to write a vertex's color to your VB if it can be trivially derived from its position, it would be less bw to calculate it in the shader with a lerp or some other blending operation.
I've done this very thing for a project of mine, and the speedup is massive on my GTX 480. Brought me from spending 50ms on rendering heightmaps to around 1ms.
Unless I am missing something you don't need to lock your index buffer and update it, you are setting it to the same values anyways. That will get rid of that 1000x2000 loop.
Also I wouldn't update the vertex buffer everyframe if the data isn't changing everyframe. Just update it whenever the data is actually updated.