From 70a860173aa9cdd007f42bdb61462621f1bf16c2 Mon Sep 17 00:00:00 2001 From: Chris Date: Sun, 16 Aug 2020 16:15:42 -0400 Subject: [PATCH] Initial commit of starter code project --- DX11Starter.sln | 31 ++ DX11Starter.vcxproj | 160 +++++++++ DX11Starter.vcxproj.filters | 50 +++ DXCore.cpp | 673 ++++++++++++++++++++++++++++++++++++ DXCore.h | 97 ++++++ Game.cpp | 311 +++++++++++++++++ Game.h | 44 +++ Main.cpp | 41 +++ PixelShader.hlsl | 34 ++ Vertex.h | 14 + VertexShader.hlsl | 64 ++++ 11 files changed, 1519 insertions(+) create mode 100644 DX11Starter.sln create mode 100644 DX11Starter.vcxproj create mode 100644 DX11Starter.vcxproj.filters create mode 100644 DXCore.cpp create mode 100644 DXCore.h create mode 100644 Game.cpp create mode 100644 Game.h create mode 100644 Main.cpp create mode 100644 PixelShader.hlsl create mode 100644 Vertex.h create mode 100644 VertexShader.hlsl diff --git a/DX11Starter.sln b/DX11Starter.sln new file mode 100644 index 0000000..d89bc90 --- /dev/null +++ b/DX11Starter.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29209.62 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DX11Starter", "DX11Starter.vcxproj", "{7B07137C-8E03-4F0C-BEDA-4C9915CD667C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Debug|x64.ActiveCfg = Debug|x64 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Debug|x64.Build.0 = Debug|x64 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Debug|x86.ActiveCfg = Debug|Win32 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Debug|x86.Build.0 = Debug|Win32 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Release|x64.ActiveCfg = Release|x64 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Release|x64.Build.0 = Release|x64 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Release|x86.ActiveCfg = Release|Win32 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {0783F28E-8634-436C-B05A-98F92CB21105} + EndGlobalSection +EndGlobal diff --git a/DX11Starter.vcxproj b/DX11Starter.vcxproj new file mode 100644 index 0000000..0e1ebf5 --- /dev/null +++ b/DX11Starter.vcxproj @@ -0,0 +1,160 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + {7B07137C-8E03-4F0C-BEDA-4C9915CD667C} + DX11Starter + 10.0 + + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + Application + true + v142 + MultiByte + + + Application + false + v142 + true + MultiByte + + + + + + + + + + + + + + + + + + + + + + + Level3 + Disabled + true + true + + + Windows + + + + + Level3 + Disabled + true + true + + + Windows + + + + + Level3 + MaxSpeed + true + true + true + true + + + Windows + true + true + + + + + Level3 + MaxSpeed + true + true + true + true + + + Windows + true + true + + + + + + + + + + + + + + + Pixel + 5.0 + Pixel + 5.0 + Pixel + 5.0 + Pixel + 5.0 + + + Vertex + 5.0 + Vertex + 5.0 + Vertex + 5.0 + Vertex + 5.0 + + + + + + \ No newline at end of file diff --git a/DX11Starter.vcxproj.filters b/DX11Starter.vcxproj.filters new file mode 100644 index 0000000..4f0fa0b --- /dev/null +++ b/DX11Starter.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {e76f01f5-08db-40d4-8720-b5b21f7ec0a3} + + + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/DXCore.cpp b/DXCore.cpp new file mode 100644 index 0000000..e7553a9 --- /dev/null +++ b/DXCore.cpp @@ -0,0 +1,673 @@ +#include "DXCore.h" + +#include +#include + +// Define the static instance variable so our OS-level +// message handling function below can talk to our object +DXCore* DXCore::DXCoreInstance = 0; + +// -------------------------------------------------------- +// The global callback function for handling windows OS-level messages. +// +// This needs to be a global function (not part of a class), but we want +// to forward the parameters to our class to properly handle them. +// -------------------------------------------------------- +LRESULT DXCore::WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + return DXCoreInstance->ProcessMessage(hWnd, uMsg, wParam, lParam); +} + +// -------------------------------------------------------- +// Constructor - Set up fields and timer +// +// hInstance - The application's OS-level handle (unique ID) +// titleBarText - Text for the window's title bar +// windowWidth - Width of the window's client (internal) area +// windowHeight - Height of the window's client (internal) area +// debugTitleBarStats - Show debug stats in the title bar, like FPS? +// -------------------------------------------------------- +DXCore::DXCore( + HINSTANCE hInstance, // The application's handle + const char* titleBarText, // Text for the window's title bar + unsigned int windowWidth, // Width of the window's client area + unsigned int windowHeight, // Height of the window's client area + bool debugTitleBarStats) // Show extra stats (fps) in title bar? +{ + // Save a static reference to this object. + // - Since the OS-level message function must be a non-member (global) function, + // it won't be able to directly interact with our DXCore object otherwise. + // - (Yes, a singleton might be a safer choice here). + DXCoreInstance = this; + + // Save params + this->hInstance = hInstance; + this->titleBarText = titleBarText; + this->width = windowWidth; + this->height = windowHeight; + this->titleBarStats = debugTitleBarStats; + + // Initialize fields + this->hasFocus = true; + + this->fpsFrameCount = 0; + this->fpsTimeElapsed = 0.0f; + this->currentTime = 0; + this->deltaTime = 0; + this->startTime = 0; + this->totalTime = 0; + + // Query performance counter for accurate timing information + __int64 perfFreq; + QueryPerformanceFrequency((LARGE_INTEGER*)&perfFreq); + perfCounterSeconds = 1.0 / (double)perfFreq; +} + +// -------------------------------------------------------- +// Destructor - Clean up (release) all DirectX references +// -------------------------------------------------------- +DXCore::~DXCore() +{ + // Note: Since we're using smart pointers (ComPtr), + // we don't need to explicitly clean up those DirectX objects + // - If we weren't using smart pointers, we'd need + // to call Release() on each DirectX object created in DXCore +} + +// -------------------------------------------------------- +// Created the actual window for our application +// -------------------------------------------------------- +HRESULT DXCore::InitWindow() +{ + // Start window creation by filling out the + // appropriate window class struct + WNDCLASS wndClass = {}; // Zero out the memory + wndClass.style = CS_HREDRAW | CS_VREDRAW; // Redraw on horizontal or vertical movement/adjustment + wndClass.lpfnWndProc = DXCore::WindowProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = hInstance; // Our app's handle + wndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // Default icon + wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); // Default arrow cursor + wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = "Direct3DWindowClass"; + + // Attempt to register the window class we've defined + if (!RegisterClass(&wndClass)) + { + // Get the most recent error + DWORD error = GetLastError(); + + // If the class exists, that's actually fine. Otherwise, + // we can't proceed with the next step. + if (error != ERROR_CLASS_ALREADY_EXISTS) + return HRESULT_FROM_WIN32(error); + } + + // Adjust the width and height so the "client size" matches + // the width and height given (the inner-area of the window) + RECT clientRect; + SetRect(&clientRect, 0, 0, width, height); + AdjustWindowRect( + &clientRect, + WS_OVERLAPPEDWINDOW, // Has a title bar, border, min and max buttons, etc. + false); // No menu bar + + // Center the window to the screen + RECT desktopRect; + GetClientRect(GetDesktopWindow(), &desktopRect); + int centeredX = (desktopRect.right / 2) - (clientRect.right / 2); + int centeredY = (desktopRect.bottom / 2) - (clientRect.bottom / 2); + + // Actually ask Windows to create the window itself + // using our settings so far. This will return the + // handle of the window, which we'll keep around for later + hWnd = CreateWindow( + wndClass.lpszClassName, + titleBarText.c_str(), + WS_OVERLAPPEDWINDOW, + centeredX, + centeredY, + clientRect.right - clientRect.left, // Calculated width + clientRect.bottom - clientRect.top, // Calculated height + 0, // No parent window + 0, // No menu + hInstance, // The app's handle + 0); // No other windows in our application + + // Ensure the window was created properly + if (hWnd == NULL) + { + DWORD error = GetLastError(); + return HRESULT_FROM_WIN32(error); + } + + // The window exists but is not visible yet + // We need to tell Windows to show it, and how to show it + ShowWindow(hWnd, SW_SHOW); + + // Return an "everything is ok" HRESULT value + return S_OK; +} + + +// -------------------------------------------------------- +// Initializes DirectX, which requires a window. This method +// also creates several DirectX objects we'll need to start +// drawing things to the screen. +// -------------------------------------------------------- +HRESULT DXCore::InitDirectX() +{ + // This will hold options for DirectX initialization + unsigned int deviceFlags = 0; + +#if defined(DEBUG) || defined(_DEBUG) + // If we're in debug mode in visual studio, we also + // want to make a "Debug DirectX Device" to see some + // errors and warnings in Visual Studio's output window + // when things go wrong! + deviceFlags |= D3D11_CREATE_DEVICE_DEBUG; +#endif + + // Create a description of how our swap + // chain should work + DXGI_SWAP_CHAIN_DESC swapDesc = {}; + swapDesc.BufferCount = 2; + swapDesc.BufferDesc.Width = width; + swapDesc.BufferDesc.Height = height; + swapDesc.BufferDesc.RefreshRate.Numerator = 60; + swapDesc.BufferDesc.RefreshRate.Denominator = 1; + swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + swapDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapDesc.Flags = 0; + swapDesc.OutputWindow = hWnd; + swapDesc.SampleDesc.Count = 1; + swapDesc.SampleDesc.Quality = 0; + swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; + swapDesc.Windowed = true; + + // Result variable for below function calls + HRESULT hr = S_OK; + + // Attempt to initialize DirectX + hr = D3D11CreateDeviceAndSwapChain( + 0, // Video adapter (physical GPU) to use, or null for default + D3D_DRIVER_TYPE_HARDWARE, // We want to use the hardware (GPU) + 0, // Used when doing software rendering + deviceFlags, // Any special options + 0, // Optional array of possible verisons we want as fallbacks + 0, // The number of fallbacks in the above param + D3D11_SDK_VERSION, // Current version of the SDK + &swapDesc, // Address of swap chain options + swapChain.GetAddressOf(), // Pointer to our Swap Chain pointer + device.GetAddressOf(), // Pointer to our Device pointer + &dxFeatureLevel, // This will hold the actual feature level the app will use + context.GetAddressOf()); // Pointer to our Device Context pointer + if (FAILED(hr)) return hr; + + // The above function created the back buffer render target + // for us, but we need a reference to it + ID3D11Texture2D* backBufferTexture = 0; + swapChain->GetBuffer( + 0, + __uuidof(ID3D11Texture2D), + (void**)&backBufferTexture); + + // Now that we have the texture, create a render target view + // for the back buffer so we can render into it. Then release + // our local reference to the texture, since we have the view. + if (backBufferTexture != 0) + { + device->CreateRenderTargetView( + backBufferTexture, + 0, + backBufferRTV.GetAddressOf()); + backBufferTexture->Release(); + } + + // Set up the description of the texture to use for the depth buffer + D3D11_TEXTURE2D_DESC depthStencilDesc = {}; + depthStencilDesc.Width = width; + depthStencilDesc.Height = height; + depthStencilDesc.MipLevels = 1; + depthStencilDesc.ArraySize = 1; + depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + depthStencilDesc.Usage = D3D11_USAGE_DEFAULT; + depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + depthStencilDesc.CPUAccessFlags = 0; + depthStencilDesc.MiscFlags = 0; + depthStencilDesc.SampleDesc.Count = 1; + depthStencilDesc.SampleDesc.Quality = 0; + + // Create the depth buffer and its view, then + // release our reference to the texture + ID3D11Texture2D* depthBufferTexture = 0; + device->CreateTexture2D(&depthStencilDesc, 0, &depthBufferTexture); + if (depthBufferTexture != 0) + { + device->CreateDepthStencilView( + depthBufferTexture, + 0, + depthStencilView.GetAddressOf()); + depthBufferTexture->Release(); + } + + // Bind the views to the pipeline, so rendering properly + // uses their underlying textures + context->OMSetRenderTargets( + 1, + backBufferRTV.GetAddressOf(), + depthStencilView.Get()); + + // Lastly, set up a viewport so we render into + // to correct portion of the window + D3D11_VIEWPORT viewport = {}; + viewport.TopLeftX = 0; + viewport.TopLeftY = 0; + viewport.Width = (float)width; + viewport.Height = (float)height; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + context->RSSetViewports(1, &viewport); + + // Return the "everything is ok" HRESULT value + return S_OK; +} + +// -------------------------------------------------------- +// When the window is resized, the underlying +// buffers (textures) must also be resized to match. +// +// If we don't do this, the window size and our rendering +// resolution won't match up. This can result in odd +// stretching/skewing. +// -------------------------------------------------------- +void DXCore::OnResize() +{ + // Release the buffers before resizing the swap chain + backBufferRTV.Reset(); + depthStencilView.Reset(); + + // Resize the underlying swap chain buffers + swapChain->ResizeBuffers( + 2, + width, + height, + DXGI_FORMAT_R8G8B8A8_UNORM, + 0); + + // Recreate the render target view for the back buffer + // texture, then release our local texture reference + ID3D11Texture2D* backBufferTexture = 0; + swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast(&backBufferTexture)); + if (backBufferTexture != 0) + { + device->CreateRenderTargetView( + backBufferTexture, + 0, + backBufferRTV.ReleaseAndGetAddressOf()); // ReleaseAndGetAddressOf() cleans up the old object before giving us the pointer + backBufferTexture->Release(); + } + + // Set up the description of the texture to use for the depth buffer + D3D11_TEXTURE2D_DESC depthStencilDesc; + depthStencilDesc.Width = width; + depthStencilDesc.Height = height; + depthStencilDesc.MipLevels = 1; + depthStencilDesc.ArraySize = 1; + depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; + depthStencilDesc.Usage = D3D11_USAGE_DEFAULT; + depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; + depthStencilDesc.CPUAccessFlags = 0; + depthStencilDesc.MiscFlags = 0; + depthStencilDesc.SampleDesc.Count = 1; + depthStencilDesc.SampleDesc.Quality = 0; + + // Create the depth buffer and its view, then + // release our reference to the texture + ID3D11Texture2D* depthBufferTexture = 0; + device->CreateTexture2D(&depthStencilDesc, 0, &depthBufferTexture); + if (depthBufferTexture != 0) + { + device->CreateDepthStencilView( + depthBufferTexture, + 0, + depthStencilView.ReleaseAndGetAddressOf()); // ReleaseAndGetAddressOf() cleans up the old object before giving us the pointer + depthBufferTexture->Release(); + } + + // Bind the views to the pipeline, so rendering properly + // uses their underlying textures + context->OMSetRenderTargets( + 1, + backBufferRTV.GetAddressOf(), // This requires a pointer to a pointer (an array of pointers), so we get the address of the pointer + depthStencilView.Get()); + + // Lastly, set up a viewport so we render into + // to correct portion of the window + D3D11_VIEWPORT viewport = {}; + viewport.TopLeftX = 0; + viewport.TopLeftY = 0; + viewport.Width = (float)width; + viewport.Height = (float)height; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + context->RSSetViewports(1, &viewport); +} + + +// -------------------------------------------------------- +// This is the main game loop, handling the following: +// - OS-level messages coming in from Windows itself +// - Calling update & draw back and forth, forever +// -------------------------------------------------------- +HRESULT DXCore::Run() +{ + // Grab the start time now that + // the game loop is running + __int64 now; + QueryPerformanceCounter((LARGE_INTEGER*)&now); + startTime = now; + currentTime = now; + previousTime = now; + + // Give subclass a chance to initialize + Init(); + + // Our overall game and message loop + MSG msg = {}; + while (msg.message != WM_QUIT) + { + // Determine if there is a message waiting + if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) + { + // Translate and dispatch the message + // to our custom WindowProc function + TranslateMessage(&msg); + DispatchMessage(&msg); + } + else + { + // Update timer and title bar (if necessary) + UpdateTimer(); + if(titleBarStats) + UpdateTitleBarStats(); + + // The game loop + Update(deltaTime, totalTime); + Draw(deltaTime, totalTime); + } + } + + // We'll end up here once we get a WM_QUIT message, + // which usually comes from the user closing the window + return (HRESULT)msg.wParam; +} + + +// -------------------------------------------------------- +// Sends an OS-level window close message to our process, which +// will be handled by our message processing function +// -------------------------------------------------------- +void DXCore::Quit() +{ + PostMessage(this->hWnd, WM_CLOSE, NULL, NULL); +} + + +// -------------------------------------------------------- +// Uses high resolution time stamps to get very accurate +// timing information, and calculates useful time stats +// -------------------------------------------------------- +void DXCore::UpdateTimer() +{ + // Grab the current time + __int64 now; + QueryPerformanceCounter((LARGE_INTEGER*)&now); + currentTime = now; + + // Calculate delta time and clamp to zero + // - Could go negative if CPU goes into power save mode + // or the process itself gets moved to another core + deltaTime = max((float)((currentTime - previousTime) * perfCounterSeconds), 0.0f); + + // Calculate the total time from start to now + totalTime = (float)((currentTime - startTime) * perfCounterSeconds); + + // Save current time for next frame + previousTime = currentTime; +} + + +// -------------------------------------------------------- +// Updates the window's title bar with several stats once +// per second, including: +// - The window's width & height +// - The current FPS and ms/frame +// - The version of DirectX actually being used (usually 11) +// -------------------------------------------------------- +void DXCore::UpdateTitleBarStats() +{ + fpsFrameCount++; + + // Only calc FPS and update title bar once per second + float timeDiff = totalTime - fpsTimeElapsed; + if (timeDiff < 1.0f) + return; + + // How long did each frame take? (Approx) + float mspf = 1000.0f / (float)fpsFrameCount; + + // Quick and dirty title bar text (mostly for debugging) + std::ostringstream output; + output.precision(6); + output << titleBarText << + " Width: " << width << + " Height: " << height << + " FPS: " << fpsFrameCount << + " Frame Time: " << mspf << "ms"; + + // Append the version of DirectX the app is using + switch (dxFeatureLevel) + { + case D3D_FEATURE_LEVEL_11_1: output << " DX 11.1"; break; + case D3D_FEATURE_LEVEL_11_0: output << " DX 11.0"; break; + case D3D_FEATURE_LEVEL_10_1: output << " DX 10.1"; break; + case D3D_FEATURE_LEVEL_10_0: output << " DX 10.0"; break; + case D3D_FEATURE_LEVEL_9_3: output << " DX 9.3"; break; + case D3D_FEATURE_LEVEL_9_2: output << " DX 9.2"; break; + case D3D_FEATURE_LEVEL_9_1: output << " DX 9.1"; break; + default: output << " DX ???"; break; + } + + // Actually update the title bar and reset fps data + SetWindowText(hWnd, output.str().c_str()); + fpsFrameCount = 0; + fpsTimeElapsed += 1.0f; +} + +// -------------------------------------------------------- +// Allocates a console window we can print to for debugging +// +// bufferLines - Number of lines in the overall console buffer +// bufferColumns - Numbers of columns in the overall console buffer +// windowLines - Number of lines visible at once in the window +// windowColumns - Number of columns visible at once in the window +// -------------------------------------------------------- +void DXCore::CreateConsoleWindow(int bufferLines, int bufferColumns, int windowLines, int windowColumns) +{ + // Our temp console info struct + CONSOLE_SCREEN_BUFFER_INFO coninfo; + + // Get the console info and set the number of lines + AllocConsole(); + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); + coninfo.dwSize.Y = bufferLines; + coninfo.dwSize.X = bufferColumns; + SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); + + SMALL_RECT rect; + rect.Left = 0; + rect.Top = 0; + rect.Right = windowColumns; + rect.Bottom = windowLines; + SetConsoleWindowInfo(GetStdHandle(STD_OUTPUT_HANDLE), TRUE, &rect); + + FILE *stream; + freopen_s(&stream, "CONIN$", "r", stdin); + freopen_s(&stream, "CONOUT$", "w", stdout); + freopen_s(&stream, "CONOUT$", "w", stderr); + + // Prevent accidental console window close + HWND consoleHandle = GetConsoleWindow(); + HMENU hmenu = GetSystemMenu(consoleHandle, FALSE); + EnableMenuItem(hmenu, SC_CLOSE, MF_GRAYED); +} + +// -------------------------------------------------------------------------- +// Gets the actual path to this executable +// +// - As it turns out, the relative path for a program is different when +// running through VS and when running the .exe directly, which makes +// it a pain to properly load files external files (like textures) +// - Running through VS: Current Dir is the *project folder* +// - Running from .exe: Current Dir is the .exe's folder +// - This has nothing to do with DEBUG and RELEASE modes - it's purely a +// Visual Studio "thing", and isn't obvious unless you know to look +// for it. In fact, it could be fixed by changing a setting in VS, but +// the option is stored in a user file (.suo), which is ignored by most +// version control packages by default. Meaning: the option must be +// changed every on every PC. Ugh. So instead, here's a helper. +// -------------------------------------------------------------------------- +std::string DXCore::GetExePath() +{ + // Assume the path is just the "current directory" for now + std::string path = ".\\"; + + // Get the real, full path to this executable + char currentDir[1024] = {}; + GetModuleFileName(0, currentDir, 1024); + + // Find the location of the last slash charaacter + char* lastSlash = strrchr(currentDir, '\\'); + if (lastSlash) + { + // End the string at the last slash character, essentially + // chopping off the exe's file name. Remember, c-strings + // are null-terminated, so putting a "zero" character in + // there simply denotes the end of the string. + *lastSlash = 0; + + // Set the remainder as the path + path = currentDir; + } + + // Toss back whatever we've found + return path; +} + + +// --------------------------------------------------- +// Same as GetExePath(), except it returns a wide character +// string, which most of the Windows API requires. +// --------------------------------------------------- +std::wstring DXCore::GetExePath_Wide() +{ + // Grab the path as a standard string + std::string path = GetExePath(); + + // Convert to a wide string + wchar_t widePath[1024] = {}; + mbstowcs_s(0, widePath, path.c_str(), 1024); + + // Create a wstring for it and return + return std::wstring(widePath); +} + + +// ---------------------------------------------------- +// Gets the full path to a given file. NOTE: This does +// NOT "find" the file, it simply concatenates the given +// relative file path onto the executable's path +// ---------------------------------------------------- +std::string DXCore::GetFullPathTo(std::string relativeFilePath) +{ + return GetExePath() + "\\" + relativeFilePath; +} + + + +// ---------------------------------------------------- +// Same as GetFullPathTo, but with wide char strings. +// +// Gets the full path to a given file. NOTE: This does +// NOT "find" the file, it simply concatenates the given +// relative file path onto the executable's path +// ---------------------------------------------------- +std::wstring DXCore::GetFullPathTo_Wide(std::wstring relativeFilePath) +{ + return GetExePath_Wide() + L"\\" + relativeFilePath; +} + + + + + +// -------------------------------------------------------- +// Handles messages that are sent to our window by the +// operating system. Ignoring these messages would cause +// our program to hang and Windows would think it was +// unresponsive. +// -------------------------------------------------------- +LRESULT DXCore::ProcessMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + // Check the incoming message and handle any we care about + switch (uMsg) + { + // This is the message that signifies the window closing + case WM_DESTROY: + PostQuitMessage(0); // Send a quit message to our own program + return 0; + + // Prevent beeping when we "alt-enter" into fullscreen + case WM_MENUCHAR: + return MAKELRESULT(0, MNC_CLOSE); + + // Prevent the overall window from becoming too small + case WM_GETMINMAXINFO: + ((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200; + ((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200; + return 0; + + // Sent when the window size changes + case WM_SIZE: + // Don't adjust anything when minimizing, + // since we end up with a width/height of zero + // and that doesn't play well with the GPU + if (wParam == SIZE_MINIMIZED) + return 0; + + // Save the new client area dimensions. + width = LOWORD(lParam); + height = HIWORD(lParam); + + // If DX is initialized, resize + // our required buffers + if (device) + OnResize(); + + return 0; + + // Is our focus state changing? + case WM_SETFOCUS: hasFocus = true; return 0; + case WM_KILLFOCUS: hasFocus = false; return 0; + case WM_ACTIVATE: hasFocus = (LOWORD(wParam) != WA_INACTIVE); return 0; + } + + // Let Windows handle any messages we're not touching + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + diff --git a/DXCore.h b/DXCore.h new file mode 100644 index 0000000..534cc1c --- /dev/null +++ b/DXCore.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include // Used for ComPtr - a smart pointer for COM objects + +// We can include the correct library files here +// instead of in Visual Studio settings if we want +#pragma comment(lib, "d3d11.lib") + +class DXCore +{ +public: + DXCore( + HINSTANCE hInstance, // The application's handle + const char* titleBarText, // Text for the window's title bar + unsigned int windowWidth, // Width of the window's client area + unsigned int windowHeight, // Height of the window's client area + bool debugTitleBarStats); // Show extra stats (fps) in title bar? + ~DXCore(); + + // Static requirements for OS-level message processing + static DXCore* DXCoreInstance; + static LRESULT CALLBACK WindowProc( + HWND hWnd, // Window handle + UINT uMsg, // Message + WPARAM wParam, // Message's first parameter + LPARAM lParam // Message's second parameter + ); + + // Internal method for message handling + LRESULT ProcessMessage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); + + // Initialization and game-loop related methods + HRESULT InitWindow(); + HRESULT InitDirectX(); + HRESULT Run(); + void Quit(); + virtual void OnResize(); + + // Pure virtual methods for setup and game functionality + virtual void Init() = 0; + virtual void Update(float deltaTime, float totalTime) = 0; + virtual void Draw(float deltaTime, float totalTime) = 0; + +protected: + HINSTANCE hInstance; // The handle to the application + HWND hWnd; // The handle to the window itself + std::string titleBarText; // Custom text in window's title bar + bool titleBarStats; // Show extra stats in title bar? + + // Size of the window's client area + unsigned int width; + unsigned int height; + + // Does our window currently have focus? + // Helpful if we want to pause while not the active window + bool hasFocus; + + // DirectX related objects and variables + D3D_FEATURE_LEVEL dxFeatureLevel; + Microsoft::WRL::ComPtr swapChain; + Microsoft::WRL::ComPtr device; + Microsoft::WRL::ComPtr context; + + Microsoft::WRL::ComPtr backBufferRTV; + Microsoft::WRL::ComPtr depthStencilView; + + // Helper function for allocating a console window + void CreateConsoleWindow(int bufferLines, int bufferColumns, int windowLines, int windowColumns); + + // Helpers for determining the actual path to the executable + std::string GetExePath(); + std::wstring GetExePath_Wide(); + + std::string GetFullPathTo(std::string relativeFilePath); + std::wstring GetFullPathTo_Wide(std::wstring relativeFilePath); + + +private: + // Timing related data + double perfCounterSeconds; + float totalTime; + float deltaTime; + __int64 startTime; + __int64 currentTime; + __int64 previousTime; + + // FPS calculation + int fpsFrameCount; + float fpsTimeElapsed; + + void UpdateTimer(); // Updates the timer for this frame + void UpdateTitleBarStats(); // Puts debug info in the title bar +}; + diff --git a/Game.cpp b/Game.cpp new file mode 100644 index 0000000..9cbc56a --- /dev/null +++ b/Game.cpp @@ -0,0 +1,311 @@ +#include "Game.h" +#include "Vertex.h" + +// Needed for a helper function to read compiled shader files from the hard drive +#pragma comment(lib, "d3dcompiler.lib") +#include + +// For the DirectX Math library +using namespace DirectX; + +// -------------------------------------------------------- +// Constructor +// +// DXCore (base class) constructor will set up underlying fields. +// DirectX itself, and our window, are not ready yet! +// +// hInstance - the application's OS-level handle (unique ID) +// -------------------------------------------------------- +Game::Game(HINSTANCE hInstance) + : DXCore( + hInstance, // The application's handle + "DirectX Game", // Text for the window's title bar + 1280, // Width of the window's client area + 720, // Height of the window's client area + true) // Show extra stats (fps) in title bar? +{ + +#if defined(DEBUG) || defined(_DEBUG) + // Do we want a console window? Probably only in debug mode + CreateConsoleWindow(500, 120, 32, 120); + printf("Console window created successfully. Feel free to printf() here.\n"); +#endif + +} + +// -------------------------------------------------------- +// Destructor - Clean up anything our game has created: +// - Release all DirectX objects created here +// - Delete any objects to prevent memory leaks +// -------------------------------------------------------- +Game::~Game() +{ + // Note: Since we're using smart pointers (ComPtr), + // we don't need to explicitly clean up those DirectX objects + // - If we weren't using smart pointers, we'd need + // to call Release() on each DirectX object created in Game + +} + +// -------------------------------------------------------- +// Called once per program, after DirectX and the window +// are initialized but before the game loop. +// -------------------------------------------------------- +void Game::Init() +{ + // Helper methods for loading shaders, creating some basic + // geometry to draw and some simple camera matrices. + // - You'll be expanding and/or replacing these later + LoadShaders(); + CreateBasicGeometry(); + + // Tell the input assembler stage of the pipeline what kind of + // geometric primitives (points, lines or triangles) we want to draw. + // Essentially: "What kind of shape should the GPU draw with our data?" + context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); +} + +// -------------------------------------------------------- +// Loads shaders from compiled shader object (.cso) files +// and also created the Input Layout that describes our +// vertex data to the rendering pipeline. +// - Input Layout creation is done here because it must +// be verified against vertex shader byte code +// - We'll have that byte code already loaded below +// -------------------------------------------------------- +void Game::LoadShaders() +{ + // Blob for reading raw data + // - This is a simplified way of handling raw data + ID3DBlob* shaderBlob; + + // Read our compiled vertex shader code into a blob + // - Essentially just "open the file and plop its contents here" + D3DReadFileToBlob( + GetFullPathTo_Wide(L"VertexShader.cso").c_str(), // Using a custom helper for file paths + &shaderBlob); + + // Create a vertex shader from the information we + // have read into the blob above + // - A blob can give a pointer to its contents, and knows its own size + device->CreateVertexShader( + shaderBlob->GetBufferPointer(), // Get a pointer to the blob's contents + shaderBlob->GetBufferSize(), // How big is that data? + 0, // No classes in this shader + vertexShader.GetAddressOf()); // The address of the ID3D11VertexShader* + + + // Create an input layout that describes the vertex format + // used by the vertex shader we're using + // - This is used by the pipeline to know how to interpret the raw data + // sitting inside a vertex buffer + // - Doing this NOW because it requires a vertex shader's byte code to verify against! + // - Luckily, we already have that loaded (the blob above) + D3D11_INPUT_ELEMENT_DESC inputElements[2] = {}; + + // Set up the first element - a position, which is 3 float values + inputElements[0].Format = DXGI_FORMAT_R32G32B32_FLOAT; // Most formats are described as color channels; really it just means "Three 32-bit floats" + inputElements[0].SemanticName = "POSITION"; // This is "POSITION" - needs to match the semantics in our vertex shader input! + inputElements[0].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; // How far into the vertex is this? Assume it's after the previous element + + // Set up the second element - a color, which is 4 more float values + inputElements[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT; // 4x 32-bit floats + inputElements[1].SemanticName = "COLOR"; // Match our vertex shader input! + inputElements[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT; // After the previous element + + // Create the input layout, verifying our description against actual shader code + device->CreateInputLayout( + inputElements, // An array of descriptions + 2, // How many elements in that array + shaderBlob->GetBufferPointer(), // Pointer to the code of a shader that uses this layout + shaderBlob->GetBufferSize(), // Size of the shader code that uses this layout + inputLayout.GetAddressOf()); // Address of the resulting ID3D11InputLayout* + + + + // Read and create the pixel shader + // - Reusing the same blob here, since we're done with the vert shader code + D3DReadFileToBlob( + GetFullPathTo_Wide(L"PixelShader.cso").c_str(), // Using a custom helper for file paths + &shaderBlob); + + device->CreatePixelShader( + shaderBlob->GetBufferPointer(), + shaderBlob->GetBufferSize(), + 0, + pixelShader.GetAddressOf()); +} + + + +// -------------------------------------------------------- +// Creates the geometry we're going to draw - a single triangle for now +// -------------------------------------------------------- +void Game::CreateBasicGeometry() +{ + // Create some temporary variables to represent colors + // - Not necessary, just makes things more readable + XMFLOAT4 red = XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f); + XMFLOAT4 green = XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f); + XMFLOAT4 blue = XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f); + + // Set up the vertices of the triangle we would like to draw + // - We're going to copy this array, exactly as it exists in memory + // over to a DirectX-controlled data structure (the vertex buffer) + // - Note: Since we don't have a camera or really any concept of + // a "3d world" yet, we're simply describing positions within the + // bounds of how the rasterizer sees our screen: [-1 to +1] on X and Y + // - This means (0,0) is at the very center of the screen. + // - These are known as "Normalized Device Coordinates" or "Homogeneous + // Screen Coords", which are ways to describe a position without + // knowing the exact size (in pixels) of the image/window/etc. + // - Long story short: Resizing the window also resizes the triangle, + // since we're describing the triangle in terms of the window itself + Vertex vertices[] = + { + { XMFLOAT3(+0.0f, +0.5f, +0.0f), red }, + { XMFLOAT3(+0.5f, -0.5f, +0.0f), blue }, + { XMFLOAT3(-0.5f, -0.5f, +0.0f), green }, + }; + + // Set up the indices, which tell us which vertices to use and in which order + // - This is somewhat redundant for just 3 vertices (it's a simple example) + // - Indices are technically not required if the vertices are in the buffer + // in the correct order and each one will be used exactly once + // - But just to see how it's done... + unsigned int indices[] = { 0, 1, 2 }; + + + // Create the VERTEX BUFFER description ----------------------------------- + // - The description is created on the stack because we only need + // it to create the buffer. The description is then useless. + D3D11_BUFFER_DESC vbd; + vbd.Usage = D3D11_USAGE_IMMUTABLE; + vbd.ByteWidth = sizeof(Vertex) * 3; // 3 = number of vertices in the buffer + vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER; // Tells DirectX this is a vertex buffer + vbd.CPUAccessFlags = 0; + vbd.MiscFlags = 0; + vbd.StructureByteStride = 0; + + // Create the proper struct to hold the initial vertex data + // - This is how we put the initial data into the buffer + D3D11_SUBRESOURCE_DATA initialVertexData; + initialVertexData.pSysMem = vertices; + + // Actually create the buffer with the initial data + // - Once we do this, we'll NEVER CHANGE THE BUFFER AGAIN + device->CreateBuffer(&vbd, &initialVertexData, vertexBuffer.GetAddressOf()); + + + + // Create the INDEX BUFFER description ------------------------------------ + // - The description is created on the stack because we only need + // it to create the buffer. The description is then useless. + D3D11_BUFFER_DESC ibd; + ibd.Usage = D3D11_USAGE_IMMUTABLE; + ibd.ByteWidth = sizeof(unsigned int) * 3; // 3 = number of indices in the buffer + ibd.BindFlags = D3D11_BIND_INDEX_BUFFER; // Tells DirectX this is an index buffer + ibd.CPUAccessFlags = 0; + ibd.MiscFlags = 0; + ibd.StructureByteStride = 0; + + // Create the proper struct to hold the initial index data + // - This is how we put the initial data into the buffer + D3D11_SUBRESOURCE_DATA initialIndexData; + initialIndexData.pSysMem = indices; + + // Actually create the buffer with the initial data + // - Once we do this, we'll NEVER CHANGE THE BUFFER AGAIN + device->CreateBuffer(&ibd, &initialIndexData, indexBuffer.GetAddressOf()); + +} + + +// -------------------------------------------------------- +// Handle resizing DirectX "stuff" to match the new window size. +// For instance, updating our projection matrix's aspect ratio. +// -------------------------------------------------------- +void Game::OnResize() +{ + // Handle base-level DX resize stuff + DXCore::OnResize(); +} + +// -------------------------------------------------------- +// Update your game here - user input, move objects, AI, etc. +// -------------------------------------------------------- +void Game::Update(float deltaTime, float totalTime) +{ + // Quit if the escape key is pressed + if (GetAsyncKeyState(VK_ESCAPE)) + Quit(); +} + +// -------------------------------------------------------- +// Clear the screen, redraw everything, present to the user +// -------------------------------------------------------- +void Game::Draw(float deltaTime, float totalTime) +{ + // Background color (Cornflower Blue in this case) for clearing + const float color[4] = { 0.4f, 0.6f, 0.75f, 0.0f }; + + // Clear the render target and depth buffer (erases what's on the screen) + // - Do this ONCE PER FRAME + // - At the beginning of Draw (before drawing *anything*) + context->ClearRenderTargetView(backBufferRTV.Get(), color); + context->ClearDepthStencilView( + depthStencilView.Get(), + D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, + 1.0f, + 0); + + + // Set the vertex and pixel shaders to use for the next Draw() command + // - These don't technically need to be set every frame + // - Once you start applying different shaders to different objects, + // you'll need to swap the current shaders before each draw + context->VSSetShader(vertexShader.Get(), 0, 0); + context->PSSetShader(pixelShader.Get(), 0, 0); + + + // Ensure the pipeline knows how to interpret the data (numbers) + // from the vertex buffer. + // - If all of your 3D models use the exact same vertex layout, + // this could simply be done once in Init() + // - However, this isn't always the case (but might be for this course) + context->IASetInputLayout(inputLayout.Get()); + + + // Set buffers in the input assembler + // - Do this ONCE PER OBJECT you're drawing, since each object might + // have different geometry. + // - for this demo, this step *could* simply be done once during Init(), + // but I'm doing it here because it's often done multiple times per frame + // in a larger application/game + UINT stride = sizeof(Vertex); + UINT offset = 0; + context->IASetVertexBuffers(0, 1, vertexBuffer.GetAddressOf(), &stride, &offset); + context->IASetIndexBuffer(indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0); + + + // Finally do the actual drawing + // - Do this ONCE PER OBJECT you intend to draw + // - This will use all of the currently set DirectX "stuff" (shaders, buffers, etc) + // - DrawIndexed() uses the currently set INDEX BUFFER to look up corresponding + // vertices in the currently set VERTEX BUFFER + context->DrawIndexed( + 3, // The number of indices to use (we could draw a subset if we wanted) + 0, // Offset to the first index we want to use + 0); // Offset to add to each index when looking up vertices + + + + // Present the back buffer to the user + // - Puts the final frame we're drawing into the window so the user can see it + // - Do this exactly ONCE PER FRAME (always at the very end of the frame) + swapChain->Present(0, 0); + + // Due to the usage of a more sophisticated swap chain, + // the render target must be re-bound after every call to Present() + context->OMSetRenderTargets(1, backBufferRTV.GetAddressOf(), depthStencilView.Get()); +} \ No newline at end of file diff --git a/Game.h b/Game.h new file mode 100644 index 0000000..db945fa --- /dev/null +++ b/Game.h @@ -0,0 +1,44 @@ +#pragma once + +#include "DXCore.h" +#include +#include // Used for ComPtr - a smart pointer for COM objects + +class Game + : public DXCore +{ + +public: + Game(HINSTANCE hInstance); + ~Game(); + + // Overridden setup and game loop methods, which + // will be called automatically + void Init(); + void OnResize(); + void Update(float deltaTime, float totalTime); + void Draw(float deltaTime, float totalTime); + +private: + + // Initialization helper methods - feel free to customize, combine, etc. + void LoadShaders(); + void CreateBasicGeometry(); + + + // Note the usage of ComPtr below + // - This is a smart pointer for objects that abide by the + // Component Object Model, which DirectX objects do + // - More info here: https://github.com/Microsoft/DirectXTK/wiki/ComPtr + + // Buffers to hold actual geometry data + Microsoft::WRL::ComPtr vertexBuffer; + Microsoft::WRL::ComPtr indexBuffer; + + // Shaders and shader-related constructs + Microsoft::WRL::ComPtr pixelShader; + Microsoft::WRL::ComPtr vertexShader; + Microsoft::WRL::ComPtr inputLayout; + +}; + diff --git a/Main.cpp b/Main.cpp new file mode 100644 index 0000000..efc8c9e --- /dev/null +++ b/Main.cpp @@ -0,0 +1,41 @@ + +#include +#include "Game.h" + +// -------------------------------------------------------- +// Entry point for a graphical (non-console) Windows application +// -------------------------------------------------------- +int WINAPI WinMain( + _In_ HINSTANCE hInstance, // The handle to this app's instance + _In_opt_ HINSTANCE hPrevInstance, // A handle to the previous instance of the app (always NULL) + _In_ LPSTR lpCmdLine, // Command line params + _In_ int nCmdShow) // How the window should be shown (we ignore this) +{ +#if defined(DEBUG) | defined(_DEBUG) + // Enable memory leak detection as a quick and dirty + // way of determining if we forgot to clean something up + // - You may want to use something more advanced, like Visual Leak Detector + _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); +#endif + + // Create the Game object using + // the app handle we got from WinMain + Game dxGame(hInstance); + + // Result variable for function calls below + HRESULT hr = S_OK; + + // Attempt to create the window for our program, and + // exit early if something failed + hr = dxGame.InitWindow(); + if(FAILED(hr)) return hr; + + // Attempt to initialize DirectX, and exit + // early if something failed + hr = dxGame.InitDirectX(); + if(FAILED(hr)) return hr; + + // Begin the message and game loop, and then return + // whatever we get back once the game loop is over + return dxGame.Run(); +} diff --git a/PixelShader.hlsl b/PixelShader.hlsl new file mode 100644 index 0000000..88e9657 --- /dev/null +++ b/PixelShader.hlsl @@ -0,0 +1,34 @@ + +// Struct representing the data we expect to receive from earlier pipeline stages +// - Should match the output of our corresponding vertex shader +// - The name of the struct itself is unimportant +// - The variable names don't have to match other shaders (just the semantics) +// - Each variable must have a semantic, which defines its usage +struct VertexToPixel +{ + // Data type + // | + // | Name Semantic + // | | | + // v v v + float4 position : SV_POSITION; + float4 color : COLOR; +}; + +// -------------------------------------------------------- +// The entry point (main method) for our pixel shader +// +// - Input is the data coming down the pipeline (defined by the struct) +// - Output is a single color (float4) +// - Has a special semantic (SV_TARGET), which means +// "put the output of this into the current render target" +// - Named "main" because that's the default the shader compiler looks for +// -------------------------------------------------------- +float4 main(VertexToPixel input) : SV_TARGET +{ + // Just return the input color + // - This color (like most values passing through the rasterizer) is + // interpolated for each pixel between the corresponding vertices + // of the triangle we're rendering + return input.color; +} \ No newline at end of file diff --git a/Vertex.h b/Vertex.h new file mode 100644 index 0000000..d48e03b --- /dev/null +++ b/Vertex.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +// -------------------------------------------------------- +// A custom vertex definition +// +// You will eventually ADD TO this, and/or make more of these! +// -------------------------------------------------------- +struct Vertex +{ + DirectX::XMFLOAT3 Position; // The position of the vertex + DirectX::XMFLOAT4 Color; // The color of the vertex +}; \ No newline at end of file diff --git a/VertexShader.hlsl b/VertexShader.hlsl new file mode 100644 index 0000000..e0bf00b --- /dev/null +++ b/VertexShader.hlsl @@ -0,0 +1,64 @@ + +// Struct representing a single vertex worth of data +// - This should match the vertex definition in our C++ code +// - By "match", I mean the size, order and number of members +// - The name of the struct itself is unimportant, but should be descriptive +// - Each variable must have a semantic, which defines its usage +struct VertexShaderInput +{ + // Data type + // | + // | Name Semantic + // | | | + // v v v + float3 position : POSITION; // XYZ position + float4 color : COLOR; // RGBA color +}; + +// Struct representing the data we're sending down the pipeline +// - Should match our pixel shader's input (hence the name: Vertex to Pixel) +// - At a minimum, we need a piece of data defined tagged as SV_POSITION +// - The name of the struct itself is unimportant, but should be descriptive +// - Each variable must have a semantic, which defines its usage +struct VertexToPixel +{ + // Data type + // | + // | Name Semantic + // | | | + // v v v + float4 position : SV_POSITION; // XYZW position (System Value Position) + float4 color : COLOR; // RGBA color +}; + +// -------------------------------------------------------- +// The entry point (main method) for our vertex shader +// +// - Input is exactly one vertex worth of data (defined by a struct) +// - Output is a single struct of data to pass down the pipeline +// - Named "main" because that's the default the shader compiler looks for +// -------------------------------------------------------- +VertexToPixel main( VertexShaderInput input ) +{ + // Set up output struct + VertexToPixel output; + + // Here we're essentially passing the input position directly through to the next + // stage (rasterizer), though it needs to be a 4-component vector now. + // - To be considered within the bounds of the screen, the X and Y components + // must be between -1 and 1. + // - The Z component must be between 0 and 1. + // - Each of these components is then automatically divided by the W component, + // which we're leaving at 1.0 for now (this is more useful when dealing with + // a perspective projection matrix, which we'll get to in the future). + output.position = float4(input.position, 1.0f); + + // Pass the color through + // - The values will be interpolated per-pixel by the rasterizer + // - We don't need to alter it here, but we do need to send it to the pixel shader + output.color = input.color; + + // Whatever we return will make its way through the pipeline to the + // next programmable stage we're using (the pixel shader for now) + return output; +} \ No newline at end of file