commit ed6303d2b9399e4c110312250bbb63d60adfa64a Author: Allen Webster Date: Fri Sep 29 18:02:40 2023 -0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d163863 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ \ No newline at end of file diff --git a/build_examples.bat b/build_examples.bat new file mode 100644 index 0000000..193e775 --- /dev/null +++ b/build_examples.bat @@ -0,0 +1,10 @@ +@echo off + +if not exist "build\" (mkdir build) + +set opts=-nologo -FC -Zi + +pushd build +cl %opts% ..\example_texture_extraction.cpp dwrite.lib gdi32.lib /Feextract +cl %opts% ..\example_rasterizer.cpp dwrite.lib gdi32.lib user32.lib opengl32.lib /Ferasterize +popd \ No newline at end of file diff --git a/dwrite_quick_start.html b/dwrite_quick_start.html new file mode 100644 index 0000000..44ac7a2 --- /dev/null +++ b/dwrite_quick_start.html @@ -0,0 +1,1070 @@ + + +DirectWrite Rasterizer Quick Start Guide + + +

DirectWrite Rasterizer Quick Start Guide

+

Allen Webster, 2018-09-06

+

+ +Microsoft's DirectWrite text rendering technology can be integrated into a UI application in a number of ways, +but the documentation on using DirectWrite for pre-rasterized font atlas based text rendering is fairly poor. +This document exists to make DirectWrite rasterization less painful by collecting techniques and API summaries in one place. + +

+

+

Rasterizer Summary

+

+

+This DirectWrite rasterizer works by rasterizing glyphs into a GDI bitmap and then blitting the glyph into our own texture atlas +which will be used in the rest of the rendering. The rasterizer as shown here will preserve the information needed for ClearType +anti-aliasing, but can be easily adapted to be a grayscale rasterizer. (But why bother with DirectWrite +if you're not trying to leverage it's built-in ClearType anti-aliasing? That's literally all it has to offer over +insert your favorite text rasterizing method.) +

+

+The trick with DirectWrite's ClearType anti-aliasing is that once you have the glyphs in your renderable texture format, you shouldn't +be blending it on render like you normally would for grayscale text. Any sub-pixel anti-aliasing scheme would require a separate per-channel +RGB blend, and DirectWrite's ClearType anti-aliasing requires another adjustment. The good news is it doesn't take anything too fancy +in a renderer to implement any of these blend rules, at least as long as you aren't getting too fancy with smoothly animated moving text or text +colors, in particular slight sub-pixel sliding is not covered by this article, and rendering text with anything other than single-color-per-glyph +greatly complicates the blending problem. +

+

+This quick start guide will be organized into the following sections: +

    +
  1. +Lists and describes the parts of the DirectWrite API that this rasterizer needs and includes a single glyph rasterization example. +
  2. +
  3. +Sets up the appropriate mental model for blending sub-pixel textures and provides techniques to cleanly and easily achieve the effect. +
  4. +
  5. +Describes the specific quirks to blending in the DirectWrite style and provides techniques to help deal with them. +
  6. +
  7. +Tips for properly handling the API, including avoiding memory leakage, understanding lifetimes, and handling subtle design issues. +
  8. +
  9. +The fully assembled example rasterizer. +
  10. +
  11. +Some issues that could be handled with higher DirectWrite versions than this rasterizer is using. +
  12. +
+

+ +

+

Getting Rasterized Textures from DirectWrite

+

+ +

+

Dependencies

+

+

+DirectWrite is a very large API with optional integration into Direct2D and GDI. +We want to initialize and depend upon the smallest possible subset of the API that we absolutely need for rasterizing, to this end the solution +I recommend depends only on DirectWrite and GDI. DirectWrite does not support returning data to you directly, it relies on one of either GDI or +D2D to do the return, and in the case of D2D it then relies, in turn, on D3D to actually do the return, so the dependency on GDI is the better option. +

+

+The simplest way to setup the rasterizer is to supply our own ttf files, but this severly limits our ability to extract information +about font families, and the various style options within a family. There are three options to fix this, one would be to use a very +restricted newer version (DirectWrite 3) which does extract this information right from the font data supplied through the ttf +(learn more), the second option is to provide all that meta data yourself along with the ttf files, +and the third is to get fonts installed with the system instead of providing the ttfs manually. +

+

+For all of the features we need in this rasterizer we only depend on the very first version of DirectWrite, but there are a few +features that would be really nice to have that require higher versions. After showing the simplest +rasterizer I will discuss the ways that newer versions could make things a little nicer if we're willing to use any of them. +

+

+The DirectWrite Versions Dependencies Table +

+ + + + + + + + + + + +
DirectWrite versions Supported OS versionHeader File Library File
DirectWrite Windows Vista SP2 or Windows 7 or higherdwrite.h dwrite.lib
DirectWrite 1 Windows 7 Platform Update or Windows 8 or higherdwrite_1.h dwrite.lib
DirectWrite 2 Windows 8.1 or higherdwrite_2.h dwrite.lib
DirectWrite 3 Windows 10dwrite_3.h dwrite.lib
+
OS Version information taken directly from Microsoft documentation without any testing.
+

+

+In addition to including and linking only one of the above DirectWrite versions, our rasterizer will also include windows.h and +link to gdi32.lib in addition to all the standard stuff we need to setup a window and an OpenGL context. +

+

+The succinct list of DirectWrite interfaces, calls, and structs used in this DirectWrite rasterizer. +

+

+ +(My apologies if any of these links go stale, or link to pages with lots of missing documentation. +I do not intend to maintain these lists. +Later I will describe the details that are relevant anyway so Microsoft's documentation is just here for backup.) + +

+ +

+The succinct list of GDI calls used in this DirectWrite rasterizer. +

+

+ +(I am not keeping links to these because Microsoft's GDI documentation seems to be really unstable these days.) + +

+ + +

+

Code Notes

+

+

+IDWriteFactory, DWriteCreateFactory +

+

+Initializing the factory interface initializes DirectWrite and provides access to methods for creating everything else, the factory will always come first. +

+

+

IDWriteFactory *dwrite_factory = 0;
+HRESULT error = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&dwrite_factory);
+assert(error == S_OK);
+

+

+IDWriteFontFile, CreateFontFileReference +

+

+A IDWriteFontFile refers to a particular file, this is the mechanism that will allow us to set the font by supplying a ttf directly. +The file name must be in UCS2, whether or not you're using windows with #define UNICODE. +

+

+

IDWriteFontFile *font_file = 0;
+HRESULT error = dwrite_factory->CreateFontFileReference(font_path, 0, &font_file);
+assert(error == S_OK);
+

+

+IDWriteFontFace, CreateFontFace +

+

+An IDWriteFontFace refers to renderable in-memory font data. This can come from various sources but in our case we're getting it from the IDWriteFontFile. +The most concerning part of the CreateFontFace call is that it asks you for the format of the file. How are you supposed to know that if you're not using +a file that you picked by hand? Well the API has nothing to say about that, it's up to you to do what you want. Since we're the ones picking the file +we're fine here, but if you'd like to adapt this rasterizer so that users can supply new fonts at runtime you'll have to decide what to do about this. +

+

+

IDWriteFontFace *font_face = 0;
+HRESULT error = dwrite_factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face);
+assert(error == S_OK);
+

+

+IDWriteRenderingParams, CreateRenderingParams, CreateCustomRenderingParams, +GetGamma, GetEnhancedContrast, GetClearTypeLevel, GetPixelGeometry, GetRenderingMode +

+

+We need an IDWriteRenderingParams to pass to the final draw method. The "custom" version lets us set all the parameters we want. +The "non-custom" version let's us get the default values for the parameters. The default values are system settings that the user +can control from the control panel and the ClearType tuner, so you can't count on it to be the same everywhere. +

+

+

IDWriteRenderingParams *default_rendering_params = 0;
+IDWriteRenderingParams *rendering_params = 0;
+HRESULT error = dwrite_factory->CreateRenderingParams(&default_rendering_params);
+assert(error == S_OK);
+error = dwrite_factory->CreateCustomRenderingParams(default_rendering_params->GetGamma(),
+                                                    default_rendering_params->GetEnhancedContrast(),
+                                                    default_rendering_params->GetClearTypeLevel(),
+                                                    default_rendering_params->GetPixelGeometry(),
+                                                    default_rendering_params->GetRenderingMode(),
+                                                    &rendering_params);
+assert(error == S_OK);
+

+

+IDWriteGdiInterop, GetGdiInterop +

+

+The IDWriteGdiInterop is simply an interface that hosts a few methods related to getting DirectWrite to work together with GDI. +Note we are not "creating" this interface we are merely "getting" it, this is our clue that we shouldn't be releasing the interface pointer later. +We are using the IDWriteGdiInterop to create a bitmap render target, it can also be used to convert between GDI font resources and +DirectWrite font interfaces if you are looking for another way to get your IDWriteFontFace. +

+

+

IDWriteGdiInterop *dwrite_gdi_interop = 0;
+HRESULT error = dwrite_factory->GetGdiInterop(&dwrite_gdi_interop);
+assert(error == S_OK);
+

+

+DWRITE_FONT_METRICS, GetMetrics +

+

+DWRITE_FONT_METRICS contains scale information for converting design units (how everything gets returned to us) to pixels (assuming we know our DPI this API +doesn't handle that for us). It also contains line spacing information and various universal dimensions, which can be useful for predicting how big the +intermediate backing buffer of the render target needs to be to always contain the entire glyph for every glyph in the font. +The texture extractor does not need this, but the full rasterizer will. +

+

+

DWRITE_FONT_METRICS font_metrics = {0};
+font_face->GetMetrics(&font_metrics);
+

+

+GetGlyphCount +

+

+This tells us the total number of glyphs the font can rasterize for us, and since glyphs are rasterized by index we can use this as the limit of a loop +that gathers baked versions of every glyph in the font. The texture extractor does not need this, but the full rasterizer will. +

+

+

uint16_t glyph_count = font_face->GetGlyphCount();
+

+

+DWRITE_GLYPH_METRICS, GetDesignGlyphMetrics +

+

+DWRITE_GLYPH_METRICS contains scale and advance information pertaining to a single glyph. I caution against relying on it to find a bounding box as +it does not take into account the softening filter that is applied to a glyph when creating the three wide sub-pixel texture. I found that the only reliable +way to get the bounding box of a glyph was the bounding box returned after rendering the glyph. However, this is still the right way to get advance information. +The texture extractor does not need this, but the full rasterizer will. +

+

+

DWRITE_GLYPH_METRICS glyph_metrics = {0};
+error = face->GetDesignGlyphMetrics(&glyph_index, 1, &glyph_metrics, FALSE);
+

+

+IDWriteBitmapRenderTarget, CreateBitmapRenderTarget +

+

+The IDWriteBitmapRenderTarget is an interface to the actual pixel data where our draw operations will be recorded. +Note that we're calling through the GDI interop pointer not the factory. +When we create the render target we set it's dimensions. It is up to us to make sure that everything we render onto the bitmap will actually fit and after +rendering to the target it's a good idea to make sure the bounding box of the glyph actually did fit in the target. +

+

+

IDWriteBitmapRenderTarget *render_target = 0;
+HRESULT error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, width, height, &render_target);
+assert(error == S_OK);
+

+

+GetMemoryDC +

+

+This method returns a GDI style HDC that will allow us to make GDI calls that render to the same backing buffer as the IDWriteBitmapRenderTarget. +

+

+

HDC dc = render_target->GetMemoryDC();
+

+

+GetGlyphIndices +

+

+The interface we are using renders glyph indices not characters/codepoints. Glyph indices are different in every font so we have to use this call +to get the glyph index for our desired codepoint before we render. +

+

+

uint32_t codepoint = '?';
+uint16_t index = 0;
+HRESULT error = font_face->GetGlyphIndices(&codepoint, 1, &index);
+assert(error == S_OK);
+

+

+DWRITE_GLYPH_RUN, DrawGlyphRun +

+

+Finally all the pieces can come together when we call DrawGlypRun. This call actually colors the bits on the backing buffer of the IDWriteBitmapRenderTarget. +

+

+

DWRITE_GLYPH_RUN glyph_run = {0};
+glyph_run.fontFace = font_face;
+glyph_run.fontEmSize = point_size*96.f/72.f;
+glyph_run.glyphCount = 1;
+glyph_run.glyphIndices = &index;
+RECT bounding_box = {0};
+HRESULT error = render_target->DrawGlyphRun(raster_target_x, raster_target_y, DWRITE_MEASURING_MODE_NATURAL, &glyph_run, rendering_params, fore_color, &bounding_box);
+assert(error == S_OK);
+

+ +

+

Code Example

+

+

example_texture_extraction.cpp

+

+In addition to acting as example code for the DirectWrite API, this texture extractor is a handy way to get a bitmap showing us exactly how a glyph appears +directly produced by DirectWrite for examination and checking if artifacts in our rasterizer come from our bugs or from DirectWrite. +

+ +

+

Blending for Sub-Pixel Anti-Aliasing

+

+

+Getting information out of DirectWrite is not the whole story, we also have to setup the render time system to actually use the sub-pixel data correctly. +

+

+A Quick Review of the Basic Idea of Sup-Pixel Anti-Aliasing +

+

+With sup-pixel anti-aliasing the whole idea is that the screen is organized into pixels with three vertical bands "RGB". If the "B" of one pixel is very +close to the "R" of the next pixel over, then, as the idea goes, a blue pixel on the left edge of a white pixel should still look white. If X represents +an "off" sub-pixel and R G and B represent "on" sub-pixels then the idea is that the following should not appear, at normal resolution, to have a blue pixel +or a red pixel, even though it technically does. +

XXBRGBRGBRXX
+

+

+A similar idea works for black on a white background. The idea is that the following pixel pattern should not appear to have a yellow pixel (RG) or +a cyan pixel (GB), even though it technically does, because we are hoping the eye will group the RG with the B to it's left, and the GB to the R +on the right. +

RGBRGXXXXXXXXGBRGB
+

+

+However, "on" and "off" is an oversimplification. What we actually get back from DirectWrite when we render white text on a black background is the intensity +of each sup-pixel for White-on-Black text. Inspecting an enlarged version of such a glyph from our extractor reveals that DirectWrite fades intensities +in and out across the boundary of the glyph. +

+ + +
+'?'; Arial; 24 pt; 8x enlargement; example_texture_extraction.cpp +
+

+So for white-on-black rendering we take the intensity of each sub-pixel and render that into the corresponding channel. For black-on-white we essentially +take the intensity of each sup-pixel and render 1-intensity into the channel. If you actually crunch the numbers on these two images you +will find they do not correspond to a direct inversion, we will discuss the quirks of DirectWrite's non-linear blending in the next section. For now hopefully +visual inspection roughly convinces you that they are basically inversions of eachother. Bright yellow becomes dark blue, mid-tone cyan becomes mid-tone +red, white becomes black, etc. +

+

+Generalizing White-on-Black and Black-on-White to Any-Color-on-Any-Color +

+

+That's how it works for white on black and black on white, but how does it generalize if we want to support any colors? We can't treat the colors we +extract from DirectWrite in the white-on-black case as just a color to be blended using the familiar alpha blending equation: +

+
out_color = alpha*fore_color + (1 - alpha)*back_color;
+

+This equation is completely wrong for us. First, there's the obvious type-system mismatch problem that we only have one 'slot' for a foreground color, but +we sort of have two foreground colors now, one sampled from the texture and the other which is the desired aparent color of the glyph. On top of that +this equation would fail even to produce the color inversion we observe for black-on-white text, so it fails the experimental test as well. I could +go on, but hopefully the point is clear. +

+

+The best way to think about blending with sub-pixel anti-aliasing is to imagine that your source texture encodes three alpha values per pixel, or if you prefer +(as I do) a "mask" for that pixel, with a separate masking value for each channel in the pixel. The mask can range from 0, meaning "never effect this channel of +this pixel", to 1, meaning "completely replace the background value with the foreground value in this channel of this pixel". +As an equation our sub-pixel blend looks like: +

+
out_r = M_r*fore_r + (1 - M_r)*back_r;
+out_g = M_g*fore_g + (1 - M_g)*back_g;
+out_b = M_b*fore_b + (1 - M_b)*back_b;
+

+In this concept of a blend equation, the M values come from the texture, the fore color is a uniform value across the entire render primitive and the back +color, as always, comes from the destination buffer. +

+

+As a quick sanity check of this equation, let's take a look at how this captures the color inversion between white-on-black and black-on-white. Note that M does +not change between the two cases, the mask values are fixed and independent of the specific render case. +

+ + + + + + + +
Variable White-on-Black Black-on-White
fore_r, fore_g, fore_b 1, 1, 1 0, 0, 0
back_r, back_g, back_b 0, 0, 0 1, 1, 1
out_r M_r*1 + (1 - M_r)*0 = M_r M_r*0 + (1 - M_r)*1 = (1 - M_r)
out_g M_g*1 + (1 - M_g)*0 = M_g M_g*0 + (1 - M_g)*1 = (1 - M_g)
out_b M_b*1 + (1 - M_b)*0 = M_b M_b*0 + (1 - M_b)*1 = (1 - M_b)
+

+How We Achieve Per-Channel Blending in OpenGL +

+

+To avoid complications to typical renderers it is important that we find a way to achieve the blending rule described without actually trying to sample from +the destination texture which, at least for OpenGL renderers, is not an ideal situation. A google search of how to make this work will yield a lot of +people suggesting that you just have to pass the background color down as a uniform, and only ever render to single color backgrounds. However there is +actually a way to get the blend to work using just the blend unit... just so long as we only render with uniform foreground colored glyphs. +In OpenGL the calls that achieve the appropriate blend are: +

+
glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
+glBlendColor(fore.r, fore.g, fore.b, fore.a);
+

+With the blend unit setup like this, the only thing left for the shader to do is to output the masking value M which it samples from the texture. +That way the constant color, the foreground color, gets multiplied by the mask color returned from the shader, and then one minus the mask color +is multiplied by the background. +

+

+There are a few things we still want to think through at this point. First there shouldn't be any linear interpolation when sampling from the texture and idealy +we shouldn't even be in a situation where we are sampling off the pixel centers anyway. We want to treat this texture as if it was finely tuned by DirectWrite +for the specific sub-pixel placement we rasterized to at bake time. Second we may still want to have text with a foreground color that supports an alpha +value besides 1. So after we sample the mask from the texture we should multiply the foreground alpha into our masks before returning them from the shader. +

+

+With all that in mind the GLSL shader we need looks something like this: +

+
smooth in vec2 uv;
+uniform sampler2D tex;
+uniform vec4 fore_color;
+layout(location = 0) out vec4 color;
+
+void main(){
+    color.rgb = texture(tex, uv);
+    color.rgb *= fore_color.a;
+    color.a = 1;
+}
+ +

+

Blending for DirectWrite

+

+

+Now we know the basics of how to blend with sub-pixel anti-aliasing in the general case, but there are some unique quirks to the ClearType style +look that DirectWrite creates that are meant to help avoid color fringing by adaptively changing the blend factors. Luckily these adjustments do +not change the blend equation that we setup. These blend equations still hold true: +

+
out_r = M_r*fore_r + (1 - M_r)*back_r;
+out_g = M_g*fore_g + (1 - M_g)*back_g;
+out_b = M_b*fore_b + (1 - M_b)*back_b;
+

+M cannot be pulled directly from the texture after all. +

+

+To say that M is just the color pulled from the texture that we get from DirectWrite when we render a white glyph onto a black background +is an oversimplification. The math in the table in the previous section suggests that rendering white-on-black gives us the M values, and we derived +that from the blend equation, so if the blend equation holds, why aren't we getting the correct M values from this approach? The answer is that the +M value at a specific channel in a specific pixel in a specific glyph is actually not a fixed value but a non-linear function of several variables. +

+

+In the previous section I focussed on the pixel-level blend equation and broke the equation out into the separate equations for each channel, but now +we are studying the function of M, and the same function applies in every channel, so now I will focus on the channel-level equation: +

+
out = M*fore + (1 - M)*back;
+

+Once we are familiar with the function M, I will put the pieces back together. +

+

+Clusters +

+

+Before I lay out how to evaluate M I have to discuss it's most unusual variable, the cluster index, or C. It turns out that DirectWrite assigns each sub-pixel +to one of seven possible clusters. I call them "clusters" because every sub-pixel of the same cluster will have identical intensity values across the glyph +given a fixed foreground color and background color for the whole glyph, so that when you make a histogram of the intensities of sub-pixels, you see a +completely discrete distribution with every sample falling into one of the seven clusters. Each cluster corresponds to a different level of intensity. +This is where we start seeing arbitrarily tuned numbers that just "are what they are". In the following table we see the M value of each cluster when +the foreground color is white and when the foreground color is black. When the foreground color is white we get the minimum value for the cluster, and when +the foreground is black we get the maximum value for the cluster. +

+ + + + + + + + + +
Cluster Index (C) M Value for Black Foreground (Max) M Value for White Foreground (Min)
0 0/255 = 0.000000000 0/6 = 0.000000000
1 97/255 = 0.380392157 1/6 = 0.166666667
2 153/255 = 0.600000000 2/6 = 0.333333333
3 191/255 = 0.749019608 3/6 = 0.500000000
4 218/255 = 0.854901961 4/6 = 0.666666667
5 239/255 = 0.937254902 5/6 = 0.833333333
6 255/255 = 1.000000000 6/6 = 1.000000000
+

+Now I can reveal the real reason why we render white-on-black in the bake phase. It's not just because that gives us the equation: +baked_out = M*1 + (1 - M)*0 = M. It's also because the M values we get can then be converted to a cluster index by simply +multiplying by six and rounding to the nearest integer. +

+

+Magical Subjective Brightness Equation +

+

+The other variable of M is some sort of "subjective brightness value" or V as I will call it from now on. There is not much to say about what V means +except that it appears to be an equation tuned to roughly give an idea of how bright a particular color appears. It assigns a different weight to each +channel of the foreground color and combines them linearly. The equation is: +

+
V = fore_r*0.5 + fore_g + fore_b*0.1875;
+

+The fact that different colors are being treated differently has the potential to be confusing so I want to emphasize again that the computation of M +is the same for all sub-pixels regardless of what color the sub-pixel is. The only difference between colors arises from the selection of the foreground +color not from the blending of sub-pixels, and even then the only difference between colors is that they have different levels of contribution to V. +

+

+A Good Approximation for M +

+

+Now we are ready to see how M is computed. M is a function of C and V. I will use Cmin and Cmax to denote the minimum and maximum values of a particular cluster. +This sections will use a linear approximation of the interpolation from Cmax down to Cmin even though DirectWrite has a bit of soft curvature in the interpolation. +

+

+The function M can be thought of as a piecewise function with respect to V, with three major pieces. +Firstly note that V ranges from 0 to 0.5 + 1 + 0.1875 = 1.6875. +In the range [0,0.839215686374509] (as in [0,214/255]) M = Cmax. +In the range [1.266666666666667,1.6875] (as in [323/255,1.6875]) M = Cmin. +In the range [0.839215686374509,1.266666666666667] we will use a linear interpolation from Cmax down to Cmin. The DirectWrite renderer here uses a strictly +decreasing set of curves, the curves are different for each cluster in degree of curvature, but all are close enough to linear that it is not particularly +concerning to discard that fine tuning for now. In order to express a linear interpolation from Cmax down to Cmin over the range +[0.839215686374509,1.266666666666667] I will use the function unlerp(A,x,B) = (x-A)/(B-A) to take the range +[0.839215686374509,1.266666666666667] to [0,1]. Then I will use the function lerp(A,x,B) = A + (B - A)*x; to take [0,1] to [Cmax,Cmin]. +To capture the flat ranges I will finally clamp the result to Cmin, Cmax with the function clamp(Mi,x,Ma) = median of {Mi,x,Ma}. +

+

+With all of that, a rough draft of our new shader code looks like: +

+
S = texture(tex, uv);
+C = int(S*6 + 0.1); // + 0.1 just in case we have some small rounding taking us below the integer we should be hitting, + 0.5 risks rounding up.
+Cmin = lookup_somehow_Cmin(C);
+Cmax = lookup_somehow_Cmax(C);
+V = fore.r*0.5 + fore.g + fore.b*0.1875;
+A = 0.839215686374509; // 214/255
+B = 1.266666666666667; // 323/255
+M = clamp(Cmin, lerp(Cmax, unlerp(A, V, B), Cmin), Cmax);
+
+

+A Better Version of an Equally Good Approximation for M +

+

+Recall that what I said about why clusters are called clusters in the section entitled clusters. If it's true that there are only seven possible values +across every sub-pixel in the entire glyph, why are we doing all this work in the shader involving linear functions and clamps? We can compute V before +we ever invoke the shader, and then we can fill a table with the values { M(0,V), M(1,V), M(2,V), ... M(6,V) } and then submit that table to +the shader as a uniform. Then all the shader has left to do is determine the cluster index for each sub-pixel and look up it's value in the table. +Our shader rough draft would then look like: +

+
S = texture(tex, uv);
+C = int(S*6 + 0.1); // + 0.1 just incase we have some small rounding taking us below the integer we should be hitting, + 0.5 risks rounding up.
+M = M_value_table[C];
+
+

+What Our Shader Really Looks Like When We Put it All Together +

+

+example_rasterizer_frag.glsl +

+
smooth in vec3 uv;
+uniform sampler2DArray tex;
+uniform float M_value_table[7];
+layout(location = 0) out vec4 mask;
+
+void main(){
+    vec3 S = texture(tex, uv).rgb;
+    int C0 = int(S.r*6 + 0.1); // + 0.1 just incase we have some small rounding taking us below the integer we should be hitting.
+    int C1 = int(S.g*6 + 0.1);
+    int C2 = int(S.b*6 + 0.1);
+    mask.rgb = vec3(M_value_table[C0],
+                    M_value_table[C1],
+                    M_value_table[C2]);
+    mask.a = 1;
+}
+
+

+Note that we need to multiply the alpha value of the foreground color into the values of M_value_table now because this shader isn't doing that +work. The nice part of this change is that with the responsibility of multiplying alpha put onto the CPU there is no need to send the foreground +color to the shader, this way the GPU only needs one copy of the color, the one in the blend unit, and since M_value_table already depends on the +other components of the foreground color this doesn't add any additional complexity to the organization of the code. +

+

+Concerns About Gamma +

+

+For any sub-pixel anti-aliasing you have to be especially careful about how you are treating colors, and DirectWrite's ClearType is no exception. The +entire effect of sub-pixel anti-aliasing revolves around emphasizing the brightness or darkness of a shape by having a little bit of contribution from +sub-pixels that would otherwise have been unaffected by the object. The brightness of a sub-pixel is tuned to ensure it contributes what it can without +appearing as another color entirely. Incorrect gamma handling can easily become a source of color fringing by failing to correctly fade out intensities +of sub-pixels. For instance imagine text renderered white-on-black where an edge pixel is tuned to have a strong red value, a medium green value, and a +soft blue value. If the values are blended in gamma space instead of in linear space the actual output will be more like strong red, soft green, very +soft blue, making the pixel appear more red than it should. +

+

+There are two stages to handling gamma correctly. First, you must make sure you are setting the gamma correction parameter for DirectWrite +to 1.0f so that it is giving you colors in linear space. DirectWrite uses it's gamma value for converting in text colors and for +converting out the final results of it's blending. Since we only end up using cluster indices, fixing this parameter technically +does not effect the correctness of our rasterizer, but if we want to inspect results from the texture extractor numerically we should make this change. +I don't have any compelling evidence that suggests DirectWrite can perform it's internal blends more quickly with gamma set to one, but this would be +the setting that enables it to optimize out the gamma conversions if it does support such an optimization. +

+

+Second, you have to remember that after doing all of your rendering in linear color space your own renderer now needs to convert back out of linear space +into gamma space. In the rasterizer presented here, I perform gamma corrections using OpenGL's SRGB textures and an intermediate framebuffer object. +

+ +

+

Carefully Handling DirectWrite, Leakage, Lifetimes, and Design Issues

+

+

+While the first section lays out all the basics for getting the rasterized bitmap so that you can take over from there, +it leaves out some important details about the API that will come up and bother you as you try to do something real with DirectWrite. +

+

+Error handling and why checking the return error code isn't always enough. +

+

+The first thing that needs to be improved in a real rasterizer is that you probably want a better method in mind for handling the case when +any one of these calls returns something other than S_OK. On top of that, it is not enough to only check the returned HRESULT, +you also need to always check the pointer you get back from a Create or Get call that returns an interface through an output pointer +parameter. Not every single method in the API has this potential but keeping track of which do and don't is a lot more trouble than just +always checking the pointer. However writing lots of checks gets tedious fast, so I have relied on these macros: +

+
#define DWCheck(error,r)        if ((error) != S_OK){ error = S_OK; r; }
+#define DWCheckPtr(error,ptr,r) if ((ptr) == 0 || (error) != S_OK){ error = S_OK; r; }
+// usage
+void foo(){
+    IDWriteThing *thing = 0;
+    HRESULT error = CreateThing(&thing);
+    DWCheckPtr(error, thing, return);
+    
+    IDWriteThing *other_thing = 0;
+    error = GetThing(&other_thing);
+    DWCheckPtr(error, other_thing, return);
+    
+    error = thing->DoOperation();
+    DWCheck(error, return);
+    
+    for (begin_loop(); good_loop(); next_loop_step()){
+        IDWriteThing *little_thing = 0;
+        error = TryLittleThing(&little_thing);
+        DWCheckPtr(error, little_thing, continue);
+    }
+}
+
+

+

+

+Make sure you are releasing everything. +

+

+All interface pointers need to be freed by calling the Release method when their use is finished. +I use the following automated release method. +

+
struct AutoReleaserClass{
+    IUnknown *ptr_member;
+    AutoReleaserClass(IUnknown *ptr){
+        ptr_member = ptr;
+    }
+    ~AutoReleaserClass(){
+        if (ptr_member != 0){
+            ptr_member->Release();
+        }
+    }
+};
+#define DeferRelease(ptr) AutoReleaserClass ptr##_releaser(ptr)
+// usage
+IDWriteThing *foo(){
+    IDWriteThing *thing = 0;
+    HRESULT error = CreateThing(&thing);
+    DeferRelease(thing);
+    DWCheckPtr(error, thing, return);
+    
+    IDWriteThing *other_thing = 0;
+    error = GetThing(&other_thing);
+    // we want other_thing to last past the end of this scope, so no DeferRelease
+    DWCheckPtr(error, other_thing, return);
+    
+    error = thing->DoOperation();
+    DWCheck(error, return);
+    
+    for (begin_loop(); good_loop(); next_loop_step()){
+        IDWriteThing *little_thing = 0;
+        error = TryLittleThing(&little_thing);
+        DeferRelease(thing);
+        DWCheckPtr(error, little_thing, continue);
+    }
+    
+    return(other_thing);
+}
+
+

+This will automatically release any pointer you pass to the DeferRelease macro, no problem, just make sure you defer the release before doing +the error check, or else you might have a valid pointer that needs to be freed but fail because of the error code, and therefore never mark the pointer +for freeing before the scope is closed. +

+

+There's a call you'd probably like to have that is missing in the first version of DirectWrite. +

+

+As described above we have to convert our text stream into a series of glyph indices via +GetGlyphIndices to create and render a glyph run via +DrawGlyphRun. Durring the initial bake of the texture data, it would be nice to build a data +structure that will allow us to map characters to glyph data for all characters. Depending on the requirements for your font handling there are several +ways you might want to structure such a mapping, but the bad news is that the oldest DirectWrite does not support some of those options very easily. +

+

+In particular if you wanted to always support all of the characters that a font supports when you bake it, the ideal solution would be to query +the font for the set of characters it can support and build a table mapping all of those characters to their indices, then baking the information for all the +indices that you need. Unfortunately, this is essentially unachievable in the oldest version of DirectWrite. You can ask for the +mapping from a character to an index, but if you're attempting to always support all of the characters that a font supports you would have to do that +character to index query for all possible characters and then check all of the results and see which ones are available. +

+

+If that doesn't sound like an option your only other choice is to keep around the instance of the DirectWrite IDWriteFontFace for your font +and pass the query through it whenever a character to index lookup needs to happen. I don't consider this the ideal solution because now we have to +interact with the API at render time not just at bake time, but it does satisfy the requirement that if the font supports the character we support it too. +The good news is you don't have to keep all of the other interfaces alive. For instance you can create and throw away the factory that was used to create +the font face, and the font face will continue to work. You're only comitting to manually freeing one interface if you use this method. +

+

+Another option is to give up on the requirement of supporting all characters. If instead you define a small set of characters that you need to support +such that it is reasonable to perform the character to index query on all of your potential characters at bake time, then you can build the full lookup +structure yourself at bake time and don't have to interact with the DirectWrite API at render time too. +

+

+Finally there's the option of going to DirectWrite 1 which has the call you need in order to support all characters and build your own lookup structure +at bake time. Learn more about that option. +

+

+For the rasterizer presented here I will use the method of keeping the IDWriteFontFace alive for the duration of the font's use. +

+

+Handling sub-pixel orientation (i.e. pixel geometry) and multi-monitor support +

+

+A screen is not necessarily oriented such that it's sub-pixels are ordered, from left to right as RGB. I could, right now, mount any one of my monitors upsidedown +and then tell windows to rotate the output to the monitor by 180 degrees, and I would suddenly have a BGR oriented monitor. The DirectWrite API refers to this +orientation as pixel geometry, and there is a parameter in the render parameters we pass to a DirectWrite draw call that tell it what orientation we want it to +render. For the example rasterizer, multi-monitor support is not included, but if we wanted to include it we would have a little bit of extra work to do. +In a multi-monitor setup, it is possible that one monitor is RGB while another is BGR. After baking a ClearType texture for RGB, rendering with the exact same +rules to a BGR monitor will create obvious color fringing. There is really only one solution that I think is worth offering for this problem. +

+

+To do multi-monitor support, always bake a RGB, no matter what any of the monitors' orientations are, and then dispatch to two different shaders depending on +the orientation of the current host monitor for the window. BGR variant of the shader only needs to swap the first and last channel once at some point. +

+

+The more tricky part is the process of figuring out the appropriate orientation for the current monitor. This can be done by querying DirectWrite via +the CreateMonitorRenderingParams call, or by looking up the registry keys under Software\Microsoft\Avalon.Graphics\. Note that +DirectWrite just reads from those keys anyway, BUT if the keys are missing DirectWrite fills in some defaults and doesn't tell you that the keys are missing. +The registry keys in question are set when a user runs the ClearType tuner application. +

+ +

+

The Example Rasterizer

+

+

+The relevant files of the rasterizer example are: +

+
example_rasterizer.cpp
+
example_gl_funcs.h
+
example_gl_defines.h
+
example_rasterizer_vert.glsl
+
example_rasterizer_frag.glsl
+ +

+

Key Points of Interest

+

+

+It takes a lot of code to get a modern OpenGL enabled window open on windows, and even more code to load up the OpenGL features we actually need. For +demonstrating a DirectWrite based rasterizer, all of that is a distraction, so this section will point directly to the areas of the example that are worth +reviewing. All code snippets are pulled from example_rasterizer.cpp. +

+

+Renderer Setup +

+

+Search for // OpenGL Setup to find the initialization of OpenGL's state. +

+

+The settings set here that are unique and critical to our rasterizer occur in this portion of the OpenGL Setup: +

// Settings
+glEnable(GL_FRAMEBUFFER_SRGB);
+glEnable(GL_BLEND);
+glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR);
+
+// sRGB Framebuffer
+GLuint frame_texture = 0;
+glGenTextures(1, &frame_texture);
+glBindTexture(GL_TEXTURE_2D, frame_texture);
+glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, window_width, window_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
+
+glGenFramebuffers(1, &framebuffer);
+glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
+glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frame_texture, 0);
+GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+assert(status == GL_FRAMEBUFFER_COMPLETE);
+
+In particular this sets up our renderer for gamma correction with and sets up the unique blend function so that our shader can return a per-channel blending mask. +

+ +

+Font Data +

+

+The data structure that will represent a baked font: +

// Font Data Structure
+
+struct Glyph_Metrics{
+    float off_x;
+    float off_y;
+    float advance;
+    float xy_w;
+    float xy_h;
+    float uv_w;
+    float uv_h;
+};
+
+struct Baked_Font{
+    IDWriteFontFace *face;
+    GLuint texture;
+    Glyph_Metrics *metrics;
+    int32_t glyph_count;
+};
+Notice that Baked_Font stores the IDWriteFontFace which it will need to convert strings into sequences of glyph indices. +

+ +

+Font Bake Phase +

+

+Search for // Font Setup to find the code that bakes the font into a texture atlas. A notable challenge with this baker is that precise information +about the bounding box of a glyph can only be retrieved after rendering the glyph. This means getting a full list of rectangles for a rectangle packing optimizer +will be more expensive than it should be, but it is still doable. This example does nothing to be careful or to conserve space and shouldn't be thought of as +a useful part of this example. +

+ +

+Font Design Units +

+

+Font metrics come back in design units. The documentation on converting to pixel units or any other unit we might can be pretty confusing, so we'll take +a quick moment to lay out how it all fits together. Note that these are meanings of these units according to this particular Microsoft API. This mental +model does not necessarily translate anywhere else. +

+ + + + + + + + + + + + + + + + + +
Unit DescriptionConversion to Pixels
Design Unit An abstract unit of glyph geometry, independent of screen, or text size, and varies in resolution between fonts.DesignUnit * Em/DesignUnit * Point/Em * Inch/Point * Pixel/Inch
Em A unit that scales relative to the visual size of text. One Em is usually about the width of a capital M.Em * Point/Em * Inch/Point * Pixel/Inch
Point A fixed unit of physical length. 1 Point = 1/72 InchPoint * Inch/Point * Pixel/Inch
DesignUnit/Em The scale of a font's design unit. Found in font_metrics.designUnitsPerEm.(DesignUnit / (DesignUnit/Em)) * Point/Em * Inch/Point * Pixel/Inch
Point/Em The point size of text. For 12 pt text, there are 12 Point/Em.Em * Point/Em * Inch/Point * Pixel/Inch
Inch/Point Always 1/72Point * Inch/Point * Pixel/Inch
Pixel/Inch Otherwise known as the DPI. Default to 96 if you are not creating a DPI aware application.Inch * Pixel/Inch
+

+In the example, the code that handles these conversions is: +

+
float pixel_per_em = point_size*(1.f/72.f)*dpi;
+float pixel_per_design_unit = pixel_per_em/((float)font_metrics.designUnitsPerEm);
+

+Since most metrics come in design units, we can now convert to pixels by just multiplying by pixel_per_design_unit however we also need +to save pixel_per_em because the call DrawGlyphRun wants to know the font size in pixels per em. +(See the line glyph_run.fontEmSize = pixel_per_em;) +

+ +

+Drawing Strings (draw_string +

+

+The draw_string call starts by iterating the ASCII input text one character at a time to build the index array: +

+
int32_t length = 0;
+for (; text[length] != 0; length += 1);
+uint16_t *indices = (uint16_t*)malloc(sizeof(uint16_t)*length);
+
+for (int32_t i = 0; i < length; i += 1){
+    uint32_t codepoint = (uint32_t)text[i];
+    font.face->GetGlyphIndices(&codepoint, 1, &indices[i]);
+}
+
+

+Notice that this API, GetGlyphIndices, takes it's text as a 32-bit codepoint array, not as a UCS2 string which is the usual for Microsoft APIs. +This is nice in that it means you don't have to encode UTF-16 to do the index lookup, but the text rendering routine would be improved by an optimized unicode +translation routine anyway. +

+

+Later in that call we build the M value table: +

+
// Compute M Values
+float V = r*0.5f + g + b*0.1875f;
+float M_value_table[7];
+
+static float Cmax_table[] = {
+    0.f,
+    0.380392157f,
+    0.600000000f,
+    0.749019608f,
+    0.854901961f,
+    0.937254902f,
+    1.f,
+};
+static float Cmin_table[] = {
+    0.f,
+    0.166666667f,
+    0.333333333f,
+    0.500000000f,
+    0.666666667f,
+    0.833333333f,
+    1.f,
+};
+
+static float A = 0.839215686374509f; // 214/255
+static float B = 1.266666666666667f; // 323/255
+float L = (V - A)/(B - A);
+
+M_value_table[0] = 0.f;
+for (int32_t i = 1; i <= 5; i += 1){
+    float Cmax = Cmax_table[i];
+    float Cmin = Cmin_table[i];
+    float M = Cmax + (Cmin - Cmax)*L;
+    if (M > Cmax){
+        M = Cmax;
+    }
+    if (M < Cmin){
+        M = Cmin;
+    }
+    M_value_table[i] = M*a;
+}
+M_value_table[6] = a;
+
+

+Review Blending for DirectWrite for an explanation of what this code is computing. +

+ + +

+

Options Available in Higher Versions

+

+

+Bonus Section: Not necessary for main DirectWrite rasterizer example +

+

+

How to Use Higher Versions

+

+

+If you want to use a version of DirectWrite besides the original, there are a couple of steps to take. +First you should look at the version table to determine the header and library files you need to include and link, +also make sure you're okay with the OS restrictions while you're there. Second you have to alter the code that gets the interface with upgraded +features that you need, as well as any interface that helps you create or get the interfaces you care about, this means you will always start by +upgarding the factory interface: +

+
IDWriteFactory1 *dwrite_factory = 0;
+HRESULT error = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory1), (IUnknown**)&dwrite_factory);
+DeferRelease(dwrite_factory);
+DWCheckPtr(error, dwrite_factory, assert(!"factory"));
+
+

+All of the interfaces this factory returns will support DirectWrite 1, but the types do not always reflect this. When you use this factory to get +a font face for instance, it will still be an IDWriteFontFace as if you used a "version 0" factory, but we can then use +QueryInterface to get the version of the interface we wanted. +

+

+IDWriteFontFace1 *font_face_1 = 0;
+HRESULT error = font_face->QueryInterface(&font_face_1);
+DeferRelease(font_face_1);
+DWCheckPtr(error, font_face_1, assert(!"font face 1"));
+
+

+Be aware that the numbers following the interface types do not necessarily match up with their DirectWrite version number, and two interfaces in the +same version may have different numbers. For example IDWriteFontCollection1 is introduced in DirectWrite 3 and requires an +IDWriteFactory3 which has a special method for obtaining the IDWriteFontCollection1 instead of the +IDWriteFontCollection. Because the versions are so complicated and frankly disorganized, you have to figure out on a case by case basis +which version you need for which interface and how the API wants you to get to that interface. +

+ +

+

Using GetUnicodeRanges at Bake Time

+

+

+In DirectWrite 1, font faces have a few new features, including GetUnicodeRanges which will return an array of ranges fully describing +the set of characters the font supports. With this feature you can now do all of the character to index queries once at bake time, build a lookup +structure of your own, and discard the font face afterwards. To use this method you have to use a version 1 factory and turn your font face into a +version 1 font face. Once you have the IDWriteFontFace1 the following code gets the set of unicode ranges: +

+
uint32_t range_count = 0;
+HRESULT error = font_face_1->GetUnicodeRanges(0, 0, &range_count);
+DWCheck(error, assert(!"GetUnicodeRanges"));
+// alloc ranges : DWRITE_UNICODE_RANGE[range_count]
+error = font_face_1->GetUnicodeRanges(range_count, ranges, &range_count); 
+DWCheck(error, assert(!"GetUnicodeRanges"));
+for (uint32_t range_i = 0; range_i < range_count; range_i += 1){
+    for (uint32_t character = ranges[range_i].first; character <= ranges[range_i].last; character += 1){
+        // whatever it takes to build the character to index lookup
+    }
+}
+
+ +

+

BitmapRenderTarget Anti-Alias Mode

+

+

+At higher level portions of the DirectWrite API, in particular with the integration to D2D, there is a setting called anti-alias mode hich allows you to +turn ClearType on and off on a case by case basis. The low level APIs in DirectWrite do not support the same feature. You can change the "ClearType level" +in the rendering parameters to 0.f, which should have the same effect. The only annoying part of this is that you have to create two rendering parameter +interfaces to switch between them since the rendering parameter is imutable after creation. Alternatively, in DirectWrite 1, you can render with an +IDWriteBitmapRenderTarget1 which has a SetTextAntialiasMode so that you can alter this setting per-call like you could with higher +level APIs. +

+ +

+

Getting Font Metadata From TTF Based Fonts

+

+

+In DirectWrite 3 it becomes possible get information like style and font family from a directly supplied ttf file. Getting information this way is always +slightly unreliable because the ttf format does not require that this information be included, and has very loose restrictions on formats. However, for the +vast majority of ttf files it does work. To use this method you will need to create an IDWriteFactory3 and use the QueryInterface +method to get an IDWriteFontFace3 from a regular font face. Then you have access to methods for getting the family name, face name, style and weight. +

+

+When you try to get a name what is actually returned to you is an interface representing the mapping from locale strings to name strings, this interface is called +IDWriteLocalizedStrings. Once you have this interface you need to figure out the index to the locale you want, then you can get the +string's length and contents: +

+
uint32_t index = 0;
+BOOL exists = false;
+HRESULT error = localized_strings->FindLocaleName(L"en-US", &index, &exists);
+assert(error == S_OK);
+assert(exists);
+uint32_t length = 0;
+error = localized_strings->GetStringLength(index, &length);
+assert(error == S_OK);
+// alloc str : wchar_t[length + 1]
+error = localized_strings->GetString(index, str, length + 1);
+assert(error == S_OK);
+
+ + + diff --git a/example-glyph-bow.png b/example-glyph-bow.png new file mode 100644 index 0000000..771e9b1 Binary files /dev/null and b/example-glyph-bow.png differ diff --git a/example-glyph.png b/example-glyph.png new file mode 100644 index 0000000..7c1501e Binary files /dev/null and b/example-glyph.png differ diff --git a/example_gl_defines.h b/example_gl_defines.h new file mode 100644 index 0000000..b304333 --- /dev/null +++ b/example_gl_defines.h @@ -0,0 +1,55 @@ +// DirectWrite rasterization example: opengl types and constants that we need that are not in GL\gl.h + +typedef char GLchar; +typedef ptrdiff_t GLsizeiptr; +typedef ptrdiff_t GLintptr; + +typedef void GLDEBUGPROC_Type(GLenum source,GLenum type,GLuint id,GLenum severity,GLsizei length,const GLchar *message,const void *userParam); +typedef GLDEBUGPROC_Type *GLDEBUGPROC; + +#define GL_FUNC(N,R,P) typedef R N##_Type P; static N##_Type *N = 0; +#include "example_gl_funcs.h" + +#define GL_CONSTANT_COLOR 0x8001 + +#define GL_CLAMP_TO_EDGE 0x812F + +#define GL_FRAMEBUFFER_UNDEFINED 0x8219 + +#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242 +#define GL_DEBUG_SEVERITY_NOTIFICATION 0x826B + +#define GL_TEXTURE0 0x84C0 + +#define GL_ARRAY_BUFFER 0x8892 + +#define GL_DYNAMIC_DRAW 0x88E8 + +#define GL_FRAGMENT_SHADER 0x8B30 +#define GL_VERTEX_SHADER 0x8B31 + +#define GL_TEXTURE_2D_ARRAY 0x8C1A + +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 + +#define GL_READ_FRAMEBUFFER 0x8CA8 +#define GL_DRAW_FRAMEBUFFER 0x8CA9 + +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED 0x8CDD + +#define GL_COLOR_ATTACHMENT0 0x8CE0 + +#define GL_FRAMEBUFFER 0x8D40 + +#define GL_FRAMEBUFFER_SRGB 0x8DB9 + +#define GL_DEBUG_SEVERITY_HIGH 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM 0x9147 +#define GL_DEBUG_SEVERITY_LOW 0x9148 + diff --git a/example_gl_funcs.h b/example_gl_funcs.h new file mode 100644 index 0000000..76ba451 --- /dev/null +++ b/example_gl_funcs.h @@ -0,0 +1,43 @@ +// DirectWrite rasterization example: opengl functions we need to manually load + +GL_FUNC(glDebugMessageControl, void, (GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint *ids, GLboolean enabled)) +GL_FUNC(glDebugMessageCallback, void, (GLDEBUGPROC callback, const void *userParam)) + +GL_FUNC(glGenFramebuffers, void, (GLsizei n, GLuint *framebuffers)) +GL_FUNC(glBindFramebuffer, void, (GLenum target, GLuint framebuffer)) +GL_FUNC(glFramebufferTexture2D, void, (GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level)) +GL_FUNC(glCheckFramebufferStatus, GLenum, (GLenum target)) +GL_FUNC(glBlitFramebuffer, void, (GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter)) + +GL_FUNC(glTexImage3D, void, (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels)); + +GL_FUNC(glCreateProgram, GLuint, (void)) +GL_FUNC(glCreateShader, GLuint, (GLenum type)) +GL_FUNC(glCompileShader, void, (GLuint shader)) +GL_FUNC(glAttachShader, void, (GLuint program, GLuint shader)) +GL_FUNC(glLinkProgram, void, (GLuint program)) +GL_FUNC(glShaderSource, void, (GLuint shader, GLsizei count, const GLchar *const*string, const GLint *length)) +GL_FUNC(glUseProgram, void, (GLuint program)) +GL_FUNC(glGetUniformLocation, GLint, (GLuint program, const GLchar *name)) +GL_FUNC(glGetAttribLocation, GLint, (GLuint program, const GLchar *name)) + +GL_FUNC(glGenBuffers, void, (GLsizei n, GLuint *buffers)) +GL_FUNC(glBindBuffer, void, (GLenum target, GLuint buffer)) +GL_FUNC(glBufferData, void, (GLenum target, GLsizeiptr size, const void *data, GLenum usage)) + +GL_FUNC(glEnableVertexAttribArray, void, (GLuint index)) +GL_FUNC(glVertexAttribPointer, void, (GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer)) + +GL_FUNC(glActiveTexture, void, (GLenum texture)) + +GL_FUNC(glUniform4f, void, (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)) +GL_FUNC(glUniform1i, void, (GLint location, GLint v0)) +GL_FUNC(glUniform1fv, void, (GLint location, GLsizei count, const GLfloat *value)) +GL_FUNC(glUniformMatrix3fv, void, (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value)) + +GL_FUNC(glGenVertexArrays, void, (GLsizei n, GLuint *arrays)) +GL_FUNC(glBindVertexArray, void, (GLuint array)) + +GL_FUNC(glBlendColor, void, (GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)) + +#undef GL_FUNC \ No newline at end of file diff --git a/example_rasterizer.cpp b/example_rasterizer.cpp new file mode 100644 index 0000000..feebd99 --- /dev/null +++ b/example_rasterizer.cpp @@ -0,0 +1,1034 @@ +/* +** Win32 Direct Write Example Program +** v1.0.0 - June 16th 2021 +** by Allen Webster allenwebster@4coder.net +** +** public domain example program +** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK +** +** *WARNING* this example has not yet been curated and refined to save +** your time if you are trying to use it for learning. It lacks detailed +** commentary and is probably sloppy in places. +** +*/ + + +// DirectWrite rasterization example + +#define UNICODE +#include +#include +#include +#include +#include +#include +typedef int32_t bool32; + +#include "example_gl_defines.h" + +HWND +window_setup(HINSTANCE hInstance); + +//////////////////////////////// + +static int32_t window_width = 800; +static int32_t window_height = 600; +static wchar_t font_path[] = L"C:\\Windows\\Fonts\\arial.ttf"; +static float point_size = 12.f; + +// This is not a guide in fighting with Windows to let you manage the DPI. Just leave this at 96 +// until you're ready for a whole separate nightmare. +static float dpi = 96.f; + +//////////////////////////////// + +struct AutoReleaserClass{ + IUnknown *ptr_member; + AutoReleaserClass(IUnknown *ptr){ + ptr_member = ptr; + } + ~AutoReleaserClass(){ + if (ptr_member != 0){ + ptr_member->Release(); + } + } +}; +#define DeferRelease(ptr) AutoReleaserClass ptr##_releaser(ptr) +#define DWCheck(error,r) if ((error) != S_OK){ error = S_OK; r; } +#define DWCheckPtr(error,ptr,r) if ((ptr) == 0 || (error) != S_OK){ error = S_OK; r; } + +// Font Data Structure + +struct Glyph_Metrics{ + float off_x; + float off_y; + float advance; + float xy_w; + float xy_h; + float uv_w; + float uv_h; +}; + +struct Baked_Font{ + IDWriteFontFace *face; + GLuint texture; + Glyph_Metrics *metrics; + int32_t glyph_count; +}; + +//////////////////////////////// + +static char vert_source[] = +"#version 330\n" +"uniform mat3 pixel_to_normal;\n" +"in vec2 position;\n" +"in vec3 tex_position;\n" +"smooth out vec3 uv;\n" +"void main(){\n" +" gl_Position.xy = (pixel_to_normal*vec3(position, 1.f)).xy;\n" +" gl_Position.z = 0.f;\n" +" gl_Position.w = 1.f;\n" +" uv = tex_position;\n" +"}\n"; + +static char frag_source[] = +"#version 330\n" +"smooth in vec3 uv;\n" +"uniform sampler2DArray tex;\n" +"uniform float M_value_table[7];\n" +"layout(location = 0) out vec4 mask;\n" +"\n" +"void main(){\n" +"vec3 S = texture(tex, uv).rgb;\n" +"int C0 = int(S.r*6 + 0.1); // + 0.1 just incase we have some small rounding taking us below the integer we should be hitting.\n" +"int C1 = int(S.g*6 + 0.1);\n" +"int C2 = int(S.b*6 + 0.1);\n" +"mask.rgb = vec3(M_value_table[C0],\n" +"M_value_table[C1],\n" +"M_value_table[C2]);\n" +"mask.a = 1;\n" +"}\n"; + +static GLuint uniform_pixel_to_normal; +static GLuint uniform_tex; +static GLuint uniform_M_value_table; + +static GLuint attrib_position; +static GLuint attrib_tex_position; + +//////////////////////////////// + +uint32_t +next_power_of_two(uint32_t x){ + if (x == 0){ + return(1); + } + else{ + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + x += 1; + return(x); + } +} + +int32_t +round_up(float x){ + int32_t r = (int32_t)x; + if ((float)r < x){ + r += 1; + } + return(r); +} + +void +draw_string(Baked_Font font, char *text, int32_t x, int32_t y, float r, float g, float b, float a){ + // Get Index Array + int32_t length = 0; + for (; text[length] != 0; length += 1); + uint16_t *indices = (uint16_t*)malloc(sizeof(uint16_t)*length); + + for (int32_t i = 0; i < length; i += 1){ + uint32_t codepoint = (uint32_t)text[i]; + font.face->GetGlyphIndices(&codepoint, 1, &indices[i]); + } + + // Fill Vertices + int32_t float_per_vertex = 5; + int32_t byte_per_vertex = float_per_vertex*sizeof(float); + int32_t vertex_per_character = 6; + int32_t total_float_count = length*vertex_per_character*float_per_vertex; + float *vertices = (float*)malloc(sizeof(float)*total_float_count); + + { + float layout_x = (float)x; + float layout_y = (float)y; + + float *vertex = vertices; + for (int32_t i = 0; i < length; i += 1){ + uint16_t index = indices[i]; + assert(index < font.glyph_count); + + float index_f = (float)(index/4); + float uv_x = 0.5f*(float)((index&1)); + float uv_y = 0.5f*(float)(((index&2) >> 1)); + Glyph_Metrics metrics = font.metrics[index]; + + for (int32_t j = 0; j < vertex_per_character; j += 1){ + float g_x = layout_x + metrics.off_x; + float g_y = layout_y + metrics.off_y; + + switch (j){ + case 0: + { + vertex[0] = g_x; + vertex[1] = g_y; + vertex[2] = uv_x; + vertex[3] = uv_y; + }break; + case 1: + case 3: + { + vertex[0] = g_x; + vertex[1] = g_y + metrics.xy_h; + vertex[2] = uv_x; + vertex[3] = uv_y + metrics.uv_h; + }break; + case 2: + case 4: + { + vertex[0] = g_x + metrics.xy_w; + vertex[1] = g_y; + vertex[2] = uv_x + metrics.uv_w; + vertex[3] = uv_y; + }break; + case 5: + { + vertex[0] = g_x + metrics.xy_w; + vertex[1] = g_y + metrics.xy_h; + vertex[2] = uv_x + metrics.uv_w; + vertex[3] = uv_y + metrics.uv_h; + }break; + } + vertex[4] = index_f; + vertex += float_per_vertex; + } + + layout_x += metrics.advance; + } + } + + // Compute M Values + float V = r*0.5f + g + b*0.1875f; + float M_value_table[7]; + + static float Cmax_table[] = { + 0.f, + 0.380392157f, + 0.600000000f, + 0.749019608f, + 0.854901961f, + 0.937254902f, + 1.f, + }; + static float Cmin_table[] = { + 0.f, + 0.166666667f, + 0.333333333f, + 0.500000000f, + 0.666666667f, + 0.833333333f, + 1.f, + }; + + static float A = 0.839215686374509f; // 214/255 + static float B = 1.266666666666667f; // 323/255 + float L = (V - A)/(B - A); + + M_value_table[0] = 0.f; + for (int32_t i = 1; i <= 5; i += 1){ + float Cmax = Cmax_table[i]; + float Cmin = Cmin_table[i]; + float M = Cmax + (Cmin - Cmax)*L; + if (M > Cmax){ + M = Cmax; + } + if (M < Cmin){ + M = Cmin; + } + M_value_table[i] = M*a; + } + M_value_table[6] = a; + + // Draw + glBlendColor(r, g, b, a); + glBufferData(GL_ARRAY_BUFFER, total_float_count*sizeof(float), vertices, GL_DYNAMIC_DRAW); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, font.texture); + glUniform1i(uniform_tex, 0); + glUniform1fv(uniform_M_value_table, 7, M_value_table); + glVertexAttribPointer(attrib_position, 2, GL_FLOAT, GL_FALSE, byte_per_vertex, 0); + glVertexAttribPointer(attrib_tex_position, 3, GL_FLOAT, GL_FALSE, byte_per_vertex, (void*)(sizeof(float)*2)); + glDrawArrays(GL_TRIANGLES, 0, vertex_per_character*length); + + free(vertices); + free(indices); +} + +void +gl_debug(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam){ + assert(!"Bad OpenGL Call!"); +} + +int +WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){ + HWND wnd = window_setup(hInstance); + + // OpenGL Setup + GLuint framebuffer = 0; + + { + // Debug + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, 0, GL_FALSE); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, 0, GL_FALSE); + glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, 0, GL_FALSE); + glDebugMessageCallback(gl_debug, 0); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + + // Settings + glEnable(GL_FRAMEBUFFER_SRGB); + glEnable(GL_BLEND); + glBlendFunc(GL_CONSTANT_COLOR, GL_ONE_MINUS_SRC_COLOR); + + // sRGB Framebuffer + GLuint frame_texture = 0; + glGenTextures(1, &frame_texture); + glBindTexture(GL_TEXTURE_2D, frame_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, window_width, window_height, 0, GL_RGB, GL_UNSIGNED_BYTE, 0); + + glGenFramebuffers(1, &framebuffer); + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frame_texture, 0); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + assert(status == GL_FRAMEBUFFER_COMPLETE); + + // Shader + GLuint shader_vert = glCreateShader(GL_VERTEX_SHADER); + GLuint shader_frag = glCreateShader(GL_FRAGMENT_SHADER); + + char *vert_source_localized = vert_source; + char *frag_source_localized = frag_source; + + glShaderSource(shader_vert, 1, &vert_source_localized, 0); + glShaderSource(shader_frag, 1, &frag_source_localized, 0); + + glCompileShader(shader_vert); + glCompileShader(shader_frag); + + GLenum error = glGetError(); + assert(error == GL_NO_ERROR); + + GLuint program = glCreateProgram(); + glAttachShader(program, shader_vert); + glAttachShader(program, shader_frag); + glLinkProgram(program); + + error = glGetError(); + assert(error == GL_NO_ERROR); + + glUseProgram(program); + + // Uniforms and Attributes + uniform_pixel_to_normal = glGetUniformLocation(program, "pixel_to_normal"); + uniform_tex = glGetUniformLocation(program, "tex"); + uniform_M_value_table = glGetUniformLocation(program, "M_value_table"); + + attrib_position = glGetAttribLocation(program, "position"); + attrib_tex_position = glGetAttribLocation(program, "tex_position"); + + float mat[9]; + mat[0] = 2.f/(float)window_width; mat[3] = 0.f; mat[6] = -1.f; + mat[1] = 0.f; mat[4] = -2.f/(float)window_height; mat[7] = 1.f, + mat[2] = 0.f; mat[5] = 0.f; mat[8] = 1.f, + glUniformMatrix3fv(uniform_pixel_to_normal, 1, GL_FALSE, mat); + + // Viewport + glViewport(0, 0, window_width, window_height); + + // Vertex Array Object + GLuint VAO = 0; + glGenVertexArrays(1, &VAO); + glBindVertexArray(VAO); + + // Data Buffer + GLuint buffer = 0; + glGenBuffers(1, &buffer); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + + glEnableVertexAttribArray(attrib_position); + glEnableVertexAttribArray(attrib_tex_position); + } + + // Font Setup + Baked_Font font = {0}; + + { + COLORREF back_color = RGB(0,0,0); + COLORREF fore_color = RGB(255,255,255); + + HRESULT error = 0; + + // Factory + IDWriteFactory *factory = 0; + error = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&factory); + DeferRelease(factory); + DWCheckPtr(error, factory, assert(!"factory")); + + // File + IDWriteFontFile *font_file = 0; + error = factory->CreateFontFileReference(font_path, 0, &font_file); + DeferRelease(font_file); + DWCheckPtr(error, font_file, assert(!"font file")); + + // Face + error = factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font.face); + // We don't use DeferRelease because we intend to keep the font face around after the baking process. + DWCheckPtr(error, font.face, assert(!"font face")); + + // Params + IDWriteRenderingParams *default_rendering_params = 0; + error = factory->CreateRenderingParams(&default_rendering_params); + DeferRelease(default_rendering_params); + DWCheckPtr(error, default_rendering_params, assert(!"rendering params")); + + FLOAT gamma = 1.f; + + IDWriteRenderingParams *rendering_params = 0; + error = factory->CreateCustomRenderingParams(gamma, + default_rendering_params->GetEnhancedContrast(), + default_rendering_params->GetClearTypeLevel(), + default_rendering_params->GetPixelGeometry(), + DWRITE_RENDERING_MODE_NATURAL, + &rendering_params); + DeferRelease(rendering_params); + DWCheckPtr(error, rendering_params, assert(!"rendering params")); + + // Interop + IDWriteGdiInterop *dwrite_gdi_interop = 0; + error = factory->GetGdiInterop(&dwrite_gdi_interop); + DeferRelease(dwrite_gdi_interop); + DWCheckPtr(error, dwrite_gdi_interop, assert(!"gdi interop")); + + // Metrics + DWRITE_FONT_METRICS font_metrics = {0}; + font.face->GetMetrics(&font_metrics); + + float pixel_per_em = point_size*(1.f/72.f)*dpi; + float pixel_per_design_unit = pixel_per_em/((float)font_metrics.designUnitsPerEm); + + int32_t raster_target_w = (int32_t)(8.f*((float)font_metrics.capHeight)*pixel_per_design_unit); + int32_t raster_target_h = (int32_t)(8.f*((float)font_metrics.capHeight)*pixel_per_design_unit); + float raster_target_x = (float)(raster_target_w/2); + float raster_target_y = (float)(raster_target_h/2); + + assert((float) ((int)(raster_target_x)) == raster_target_x); + assert((float) ((int)(raster_target_y)) == raster_target_y); + + // Glyph Count + font.glyph_count = font.face->GetGlyphCount(); + + // Render Target + IDWriteBitmapRenderTarget *render_target = 0; + error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, raster_target_w, raster_target_h, &render_target); + HDC dc = render_target->GetMemoryDC(); + + // Clear the Render Target + { + HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); + SetDCPenColor(dc, back_color); + SelectObject(dc, GetStockObject(DC_BRUSH)); + SetDCBrushColor(dc, back_color); + Rectangle(dc, 0, 0, raster_target_w, raster_target_h); + SelectObject(dc, original); + } + + // Allocate CPU Side Atlas + int32_t atlas_w = 4*(int32_t)(((float)font_metrics.capHeight)*pixel_per_design_unit); + int32_t atlas_h = 4*(int32_t)(((float)font_metrics.capHeight)*pixel_per_design_unit); + if (atlas_w < 16){ + atlas_w = 16; + } + else{ + atlas_w = next_power_of_two(atlas_w); + } + if (atlas_h < 256){ + atlas_h = 256; + } + else{ + atlas_h = next_power_of_two(atlas_h); + } + int32_t atlas_c = (font.glyph_count + 3)/4; + int32_t atlas_slice_size = atlas_w*atlas_h*3; + int32_t atlas_memory_size = atlas_slice_size*atlas_c; + uint8_t *atlas_memory = (uint8_t*)malloc(atlas_memory_size); + memset(atlas_memory, 0, atlas_memory_size); + + // Allocate the Metric Data + font.metrics = (Glyph_Metrics*)malloc(sizeof(Glyph_Metrics)*font.glyph_count); + memset(font.metrics, 0, sizeof(Glyph_Metrics)*font.glyph_count); + + // Fill the CPU Side Atlas and Metric Data + for (uint16_t glyph_index = 0; glyph_index < font.glyph_count; glyph_index += 1){ + // Render the Glyph Into the Target + DWRITE_GLYPH_RUN glyph_run = {0}; + glyph_run.fontFace = font.face; + glyph_run.fontEmSize = pixel_per_em; + glyph_run.glyphCount = 1; + glyph_run.glyphIndices = &glyph_index; + RECT bounding_box = {0}; + error = render_target->DrawGlyphRun(raster_target_x, raster_target_y, + DWRITE_MEASURING_MODE_NATURAL, &glyph_run, rendering_params, RGB(255, 255, 255), &bounding_box); + DWCheck(error, continue); + + assert(0 <= bounding_box.left); + assert(0 <= bounding_box.top); + assert(bounding_box.right <= raster_target_w); + assert(bounding_box.bottom <= raster_target_h); + + // Compute Our Glyph Metrics + DWRITE_GLYPH_METRICS glyph_metrics = {0}; + error = font.face->GetDesignGlyphMetrics(&glyph_index, 1, &glyph_metrics, false); + DWCheck(error, continue); + + float off_x = (float)bounding_box.left - raster_target_x; + float off_y = (float)bounding_box.top - raster_target_y; + float advance = ((float)glyph_metrics.advanceWidth)*pixel_per_design_unit; + int32_t tex_w = bounding_box.right - bounding_box.left; + int32_t tex_h = bounding_box.bottom - bounding_box.top; + + font.metrics[glyph_index].off_x = off_x; + font.metrics[glyph_index].off_y = off_y; + font.metrics[glyph_index].advance = (float)round_up(advance); + font.metrics[glyph_index].xy_w = (float)tex_w; + font.metrics[glyph_index].xy_h = (float)tex_h; + font.metrics[glyph_index].uv_w = (float)tex_w/(float)atlas_w; + font.metrics[glyph_index].uv_h = (float)tex_h/(float)atlas_h; + + // Get the Bitmap + HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); + DIBSECTION dib = {0}; + GetObject(bitmap, sizeof(dib), &dib); + + // Blit the Bitmap Into Our CPU Side Atlas + int32_t x_slice_offset = (3*atlas_w/2)*(glyph_index&1); + int32_t y_slice_offset = (3*atlas_w*atlas_h/2)*((glyph_index&2) >> 1); + uint8_t *atlas_slice = atlas_memory + atlas_slice_size*(glyph_index/4) + x_slice_offset + y_slice_offset; + + { + assert(dib.dsBm.bmBitsPixel == 32); + int32_t in_pitch = dib.dsBm.bmWidthBytes; + int32_t out_pitch = atlas_w*3; + uint8_t *in_line = (uint8_t*)dib.dsBm.bmBits + bounding_box.left*4 + bounding_box.top*in_pitch; + uint8_t *out_line = atlas_slice; + for (int32_t y = 0; y < tex_h; y += 1){ + uint8_t *in_pixel = in_line; + uint8_t *out_pixel = out_line; + for (int32_t x = 0; x < tex_w; x += 1){ + out_pixel[0] = in_pixel[2]; + out_pixel[1] = in_pixel[1]; + out_pixel[2] = in_pixel[0]; + in_pixel += 4; + out_pixel += 3; + } + in_line += in_pitch; + out_line += out_pitch; + } + } + + // Clear the Render Target + { + HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); + SetDCPenColor(dc, back_color); + SelectObject(dc, GetStockObject(DC_BRUSH)); + SetDCBrushColor(dc, back_color); + Rectangle(dc, + bounding_box.left, bounding_box.top, + bounding_box.right, bounding_box.bottom); + SelectObject(dc, original); + } + } + + // Allocate and Fill the GPU Side Atlas + glGenTextures(1, &font.texture); + glBindTexture(GL_TEXTURE_2D_ARRAY, font.texture); + glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGB, atlas_w, atlas_h, atlas_c, 0, GL_RGB, GL_UNSIGNED_BYTE, atlas_memory); + + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Free CPU Side Atlas + free(atlas_memory); + atlas_memory = 0; + } + + int32_t mode = 0; + bool32 paused = false; + for (;;){ + MSG msg = {0}; + for (;PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);){ + TranslateMessage(&msg); + DispatchMessage(&msg); + if (msg.message == WM_KEYDOWN){ + if (msg.wParam == VK_SPACE){ + // Check if this key just got pressed + if (((msg.lParam >> 30) & 1) == 0){ + paused = !paused; + } + } + } + } + + HDC dc = GetDC(wnd); + + glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); + + enum{ + TB_Black, + TB_White, + TB_Red, + TB_Green, + TB_Blue, + TB_Yellow, + TB_Cyan, + TB_Purple, + TB_COUNT, + }; + + enum{ + TF_Gray, + TF_RGB, + TF_YCP, + TF_AlphaGray, + TF_AlphaRGB, + TF_AlphaYCP, + TF_COUNT, + }; + + int32_t mode_index = mode/16; + int32_t bmode = (mode_index/TF_COUNT)%TB_COUNT; + int32_t fmode = mode_index%TF_COUNT; + + float pop_r = 0.f; + float pop_g = 0.f; + float pop_b = 0.f; +#define SetPopColor(r,g,b) pop_r = (r), pop_g = (g), pop_b = (b) + switch (bmode){ + case TB_Black: + { + glClearColor(0.f, 0.f, 0.f, 1.f); + SetPopColor(1.f, 1.f, 1.f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = Black", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_White: + { + glClearColor(1.f, 1.f, 1.f, 1.f); + SetPopColor(0.f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = White", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_Red: + { + glClearColor(0.5f, 0.f, 0.f, 1.f); + SetPopColor(0.f, 0.5f, 0.5f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = (.5,0,0)", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_Green: + { + glClearColor(0.f, 0.5f, 0.f, 1.f); + SetPopColor(0.5f, 0.f, 0.5f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = (0,.5,0)", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_Blue: + { + glClearColor(0.f, 0.f, 0.5f, 1.f); + SetPopColor(0.5f, 0.5f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = (0,0,.5)", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_Yellow: + { + glClearColor(0.5f, 0.5f, 0.f, 1.f); + SetPopColor(0.f, 0.f, 0.5f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = (.5,.5,0)", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_Cyan: + { + glClearColor(0.f, 0.5f, 0.5f, 1.f); + SetPopColor(0.5f, 0.f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = (0,.5,.5)", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TB_Purple: + { + glClearColor(0.5f, 0.f, 0.5f, 1.f); + SetPopColor(0.f, 0.5f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + draw_string(font, "Back = (.5,0,.5)", 300, 60, pop_r, pop_g, pop_b, 1.f); + }break; + } + + switch (fmode){ + case TF_Gray: + { + for (int32_t i = 1; i <= 6; i += 1){ + float v = (i - 1)/5.f; + draw_string(font, "DirectWrite rasterizer testing", 50, i*80 + 40, v, v, v, 1.f); + } + draw_string(font, "Fore = Grays", 550, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TF_RGB: + { + for (int32_t i = 0; i < 3; i += 1){ + float v[3] = {0.f, 0.f, 0.f}; + v[i] = 0.5f; + draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, 120, v[0], v[1], v[2], 1.f); + } + draw_string(font, "Fore = Red Green Blue", 550, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TF_YCP: + { + for (int32_t i = 0; i < 3; i += 1){ + float v[3] = {0.5f, 0.5f, 0.5f}; + v[(i + 2)%3] = 0.f; + draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, 120, v[0], v[1], v[2], 1.f); + } + draw_string(font, "Fore = Yellow Cyan Purple", 550, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TF_AlphaGray: + { + for (int32_t j = 1; j <= 6; j += 1){ + float a = (j - 1)/5.f; + draw_string(font, "DirectWrite rasterizer testing", 50, j*80 + 40, 1.f, 1.f, 1.f, a); + draw_string(font, "DirectWrite rasterizer testing", 300, j*80 + 40, 0.f, 0.f, 0.f, a); + } + draw_string(font, "Fore = Alpha Black and White", 550, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TF_AlphaRGB: + { + for (int32_t j = 1; j <= 6; j += 1){ + float a = (j - 1)/5.f; + for (int32_t i = 0; i < 3; i += 1){ + float v[3] = {0.f, 0.f, 0.f}; + v[i] = 0.5f; + draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, j*80 + 40, v[0], v[1], v[2], a); + } + } + draw_string(font, "Fore = Alpha Red Green Blue", 550, 60, pop_r, pop_g, pop_b, 1.f); + }break; + + case TF_AlphaYCP: + { + for (int32_t j = 1; j <= 6; j += 1){ + float a = (j - 1)/5.f; + for (int32_t i = 0; i < 3; i += 1){ + float v[3] = {0.5f, 0.5f, 0.5f}; + v[(i + 2)%3] = 0.f; + draw_string(font, "DirectWrite rasterizer testing", 50 + 250*i, j*80 + 40, v[0], v[1], v[2], a); + } + } + draw_string(font, "Fore = Alpha Yellow Cyan Purple", 550, 60, pop_r, pop_g, pop_b, 1.f); + }break; + } + + if (!paused){ + draw_string(font, "Press space to pause cycle", 50, 60, pop_r, pop_g, pop_b, 1.f); + } + else{ + draw_string(font, "Press space to resume cycle", 50, 60, pop_r, pop_g, pop_b, 1.f); + } + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer); + glBlitFramebuffer(0, 0, window_width, window_height, 0, 0, window_width, window_height, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + SwapBuffers(dc); + + Sleep(100); + if (!paused){ + mode += 1; + } + ShowWindow(wnd, TRUE); + } + + return(0); +} + +//////////////////////////////// + +#define WGL_NUMBER_PIXEL_FORMATS_ARB 0x2000 +#define WGL_DRAW_TO_WINDOW_ARB 0x2001 +#define WGL_DRAW_TO_BITMAP_ARB 0x2002 +#define WGL_ACCELERATION_ARB 0x2003 +#define WGL_NEED_PALETTE_ARB 0x2004 +#define WGL_NEED_SYSTEM_PALETTE_ARB 0x2005 +#define WGL_SWAP_LAYER_BUFFERS_ARB 0x2006 +#define WGL_SWAP_METHOD_ARB 0x2007 +#define WGL_NUMBER_OVERLAYS_ARB 0x2008 +#define WGL_NUMBER_UNDERLAYS_ARB 0x2009 +#define WGL_TRANSPARENT_ARB 0x200A +#define WGL_TRANSPARENT_RED_VALUE_ARB 0x2037 +#define WGL_TRANSPARENT_GREEN_VALUE_ARB 0x2038 +#define WGL_TRANSPARENT_BLUE_VALUE_ARB 0x2039 +#define WGL_TRANSPARENT_ALPHA_VALUE_ARB 0x203A +#define WGL_TRANSPARENT_INDEX_VALUE_ARB 0x203B +#define WGL_SHARE_DEPTH_ARB 0x200C +#define WGL_SHARE_STENCIL_ARB 0x200D +#define WGL_SHARE_ACCUM_ARB 0x200E +#define WGL_SUPPORT_GDI_ARB 0x200F +#define WGL_SUPPORT_OPENGL_ARB 0x2010 +#define WGL_DOUBLE_BUFFER_ARB 0x2011 +#define WGL_STEREO_ARB 0x2012 +#define WGL_PIXEL_TYPE_ARB 0x2013 +#define WGL_COLOR_BITS_ARB 0x2014 +#define WGL_RED_BITS_ARB 0x2015 +#define WGL_RED_SHIFT_ARB 0x2016 +#define WGL_GREEN_BITS_ARB 0x2017 +#define WGL_GREEN_SHIFT_ARB 0x2018 +#define WGL_BLUE_BITS_ARB 0x2019 +#define WGL_BLUE_SHIFT_ARB 0x201A +#define WGL_ALPHA_BITS_ARB 0x201B +#define WGL_ALPHA_SHIFT_ARB 0x201C +#define WGL_ACCUM_BITS_ARB 0x201D +#define WGL_ACCUM_RED_BITS_ARB 0x201E +#define WGL_ACCUM_GREEN_BITS_ARB 0x201F +#define WGL_ACCUM_BLUE_BITS_ARB 0x2020 +#define WGL_ACCUM_ALPHA_BITS_ARB 0x2021 +#define WGL_DEPTH_BITS_ARB 0x2022 +#define WGL_STENCIL_BITS_ARB 0x2023 +#define WGL_AUX_BUFFERS_ARB 0x2024 + +#define WGL_NO_ACCELERATION_ARB 0x2025 +#define WGL_GENERIC_ACCELERATION_ARB 0x2026 +#define WGL_FULL_ACCELERATION_ARB 0x2027 + +#define WGL_SWAP_EXCHANGE_ARB 0x2028 +#define WGL_SWAP_COPY_ARB 0x2029 +#define WGL_SWAP_UNDEFINED_ARB 0x202A + +#define WGL_TYPE_RGBA_ARB 0x202B +#define WGL_TYPE_COLORINDEX_ARB 0x202C + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 + +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 + +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 + +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 + +static void* +get_procedure(char *name){ + void *result = wglGetProcAddress(name); + return(result); +} + +typedef HGLRC wglCreateContextAttribsARB_Function(HDC hDC, + HGLRC hShareContext, + const int *attribList); + +typedef BOOL wglChoosePixelFormatARB_Function(HDC hdc, + const int *piAttribIList, + const FLOAT *pfAttribFList, + UINT nMaxFormats, + int *piFormats, + UINT *nNumFormats); + +LRESULT +window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam){ + LRESULT result = 0; + switch (uMsg){ + case WM_CREATE: + {}break; + + case WM_CLOSE: + case WM_DESTROY: + { + ExitProcess(0); + }break; + + default: + { + result = DefWindowProc(hwnd, uMsg, wParam, lParam); + }break; + } + return(result); +} + +HWND +window_setup(HINSTANCE hInstance){ +#define L_STARTER_WINDOW_CLASS_NAME L"starter-window" +#define L_REAL_WINDOW_CLASS_NAME L"window" + wchar_t title[] = L"Example DirectWrite Based Rasterizer"; + + // NOTE(allen): Setup a starter window + WNDCLASSEX starter_window_class = {0}; + starter_window_class.cbSize = sizeof(starter_window_class); + starter_window_class.lpfnWndProc = DefWindowProc; + starter_window_class.hInstance = hInstance; + starter_window_class.lpszClassName = L_STARTER_WINDOW_CLASS_NAME; + ATOM starter_window_class_atom = RegisterClassEx(&starter_window_class); + assert(starter_window_class_atom != 0); + + HWND starter_window = CreateWindowEx(0, L_STARTER_WINDOW_CLASS_NAME, L"", + 0, + CW_USEDEFAULT, CW_USEDEFAULT, + CW_USEDEFAULT, CW_USEDEFAULT, + NULL, + NULL, + hInstance, + 0); + + assert(starter_window != 0); + + HDC hdc = GetDC(starter_window); + assert(hdc != 0); + + PIXELFORMATDESCRIPTOR px_format = {0}; + px_format.nSize = sizeof(px_format); + px_format.nVersion = 1; + px_format.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + px_format.iPixelType = PFD_TYPE_RGBA; + px_format.cColorBits = 24; + + int32_t pixel_format_index = ChoosePixelFormat(hdc, &px_format); + assert(pixel_format_index != 0); + + bool32 success = DescribePixelFormat(hdc, pixel_format_index, sizeof(px_format), &px_format); + assert(success); + success = SetPixelFormat(hdc, pixel_format_index, &px_format); + assert(success); + + HGLRC starter_context = wglCreateContext(hdc); + assert(starter_context != 0); + + wglMakeCurrent(hdc, starter_context); + + // NOTE(allen): Get extensions from the starter window + wglCreateContextAttribsARB_Function *wglCreateContextAttribsARB = (wglCreateContextAttribsARB_Function*)get_procedure("wglCreateContextAttribsARB"); + + wglChoosePixelFormatARB_Function *wglChoosePixelFormatARB = (wglChoosePixelFormatARB_Function*)get_procedure("wglChoosePixelFormatARB"); + + // NOTE(allen): Setup the real window + WNDCLASSEX real_window_class = {0}; + real_window_class.cbSize = sizeof(real_window_class); + real_window_class.style = CS_HREDRAW | CS_VREDRAW; + real_window_class.lpfnWndProc = window_proc; + real_window_class.hInstance = hInstance; + real_window_class.hIcon = LoadIcon(NULL, IDI_APPLICATION); + real_window_class.hCursor = LoadCursor(NULL, IDC_ARROW); + real_window_class.lpszClassName = L_REAL_WINDOW_CLASS_NAME; + real_window_class.hIconSm = NULL; + ATOM real_window_class_atom = RegisterClassEx(&real_window_class); + assert(real_window_class_atom != 0); + + RECT window_rect = {0, 0, window_width, window_height}; + AdjustWindowRect(&window_rect, WS_OVERLAPPED, FALSE); + + uint32_t real_style = WS_OVERLAPPEDWINDOW; + HWND real_window = CreateWindowEx(0, L_REAL_WINDOW_CLASS_NAME, (wchar_t*)title, + real_style, + CW_USEDEFAULT, CW_USEDEFAULT, + window_rect.right - window_rect.left, + window_rect.bottom - window_rect.top, + NULL, + NULL, + hInstance, + 0); + + DWORD error_code = GetLastError(); + + assert(real_window != 0); + + HDC real_hdc = GetDC(real_window); + assert(real_hdc != 0); + + int32_t px_format_attributes[] = { + WGL_DRAW_TO_WINDOW_ARB, TRUE, + WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB, + WGL_SUPPORT_OPENGL_ARB, TRUE, + WGL_DOUBLE_BUFFER_ARB, TRUE, + WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, + 0, + }; + int32_t real_pixel_format_index = 0; + uint32_t number_of_formats = 0; + success = wglChoosePixelFormatARB(hdc, px_format_attributes, 0, + 1, &real_pixel_format_index, &number_of_formats); + assert(success); + assert(number_of_formats != 0); + + PIXELFORMATDESCRIPTOR real_px_format = {0}; + DescribePixelFormat(hdc, real_pixel_format_index, sizeof(real_px_format), &real_px_format); + success = SetPixelFormat(real_hdc, real_pixel_format_index, &real_px_format); + assert(success); + + int32_t context_attributes[] = { + WGL_CONTEXT_MAJOR_VERSION_ARB, 3, + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, + WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, + 0, + }; + HGLRC real_context = wglCreateContextAttribsARB(real_hdc, 0, context_attributes); + assert(real_context != 0); + wglMakeCurrent(real_hdc, real_context); + + ReleaseDC(real_window, real_hdc); + + // NOTE(allen): Close the starter window + ReleaseDC(starter_window, hdc); + success = wglDeleteContext(starter_context); + assert(success); + DestroyWindow(starter_window); + + // NOTE(allen): Load functions +#define GL_FUNC(N,R,P) N = (N##_Type*)get_procedure(#N); +#include "example_gl_funcs.h" +#define GL_FUNC(N,R,P) assert(N != 0); +#include "example_gl_funcs.h" + + return(real_window); +} + diff --git a/example_rasterizer_frag.glsl b/example_rasterizer_frag.glsl new file mode 100644 index 0000000..4f5caac --- /dev/null +++ b/example_rasterizer_frag.glsl @@ -0,0 +1,21 @@ +#version 330 +// DirectWrite rasterization example: fragment shader +// This file is only included for reference, GLSL code is stuffed into the rasterizer inline. + +smooth in vec3 uv; +uniform sampler2DArray tex; +uniform float M_value_table[7]; +layout(location = 0) out vec4 mask; + +void main(){ + vec3 S = texture(tex, uv).rgb; + int C0 = int(S.r*6 + 0.1); // + 0.1 just incase we have some small rounding taking us below the integer we should be hitting. + int C1 = int(S.g*6 + 0.1); + int C2 = int(S.b*6 + 0.1); + mask.rgb = vec3(M_value_table[C0], + M_value_table[C1], + M_value_table[C2]); + mask.a = 1; +} + + diff --git a/example_rasterizer_vert.glsl b/example_rasterizer_vert.glsl new file mode 100644 index 0000000..898566a --- /dev/null +++ b/example_rasterizer_vert.glsl @@ -0,0 +1,15 @@ +#version 330 +// DirectWrite rasterization example: vertex shader +// This file is only included for reference, GLSL code is stuffed into the rasterizer inline. + +uniform mat3 pixel_to_normal; +in vec2 position; +in vec3 tex_position; +smooth out vec3 uv; +void main(){ + gl_Position.xy = (pixel_to_normal*vec3(position, 1.f)).xy; + gl_Position.z = 0.f; + gl_Position.w = 1.f; + uv = tex_position; +} + diff --git a/example_texture_extraction.cpp b/example_texture_extraction.cpp new file mode 100644 index 0000000..4e79374 --- /dev/null +++ b/example_texture_extraction.cpp @@ -0,0 +1,208 @@ +/* +** Win32 Direct Write Example Program +** v1.0.0 - June 16th 2021 +** by Allen Webster allenwebster@4coder.net +** +** public domain example program +** NO WARRANTY IMPLIED; USE AT YOUR OWN RISK +** +** *WARNING* this example has not yet been curated and refined to save +** your time if you are trying to use it for learning. It lacks detailed +** commentary and is probably sloppy in places. +** +*/ + +// DirectWrite texture extraction example + +#include +#include +#include +#include +#include + +// Bitmap structs +#pragma pack(push, 1) +struct Header{ + char sig[2]; + uint32_t file_size; + uint32_t reserved; + uint32_t data_offset; +}; + +struct Info_Header{ + uint32_t size; + uint32_t width; + uint32_t height; + uint16_t planes; + uint16_t bits_per_pixel; + uint32_t compression; + uint32_t image_size; + uint32_t x_pixels_per_meter; + uint32_t y_pixels_per_meter; + uint32_t colors_used; + uint32_t important_colors; +}; + +struct Color_Table{ + uint8_t R; + uint8_t G; + uint8_t B; + uint8_t A; +}; +#pragma pack(pop) + +int main(){ + COLORREF back_color = RGB(0,0,0); + COLORREF fore_color = RGB(255,0,0); + int32_t raster_target_w = 200; + int32_t raster_target_h = 200; + float raster_target_x = 100.f; + float raster_target_y = 100.f; + wchar_t font_path[] = L"C:\\Windows\\Fonts\\arial.ttf"; + float point_size = 12.f; + uint32_t codepoint = '?'; + char *test_output_file_name = "test.bmp"; + + HRESULT error = 0; + + // DWrite Factory + IDWriteFactory *dwrite_factory = 0; + error = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&dwrite_factory); + assert(error == S_OK); + + // DWrite Font File Reference + IDWriteFontFile *font_file = 0; + error = dwrite_factory->CreateFontFileReference(font_path, 0, &font_file); + assert(error == S_OK); + + // DWrite Font Face + IDWriteFontFace *font_face = 0; + error = dwrite_factory->CreateFontFace(DWRITE_FONT_FACE_TYPE_TRUETYPE, 1, &font_file, 0, DWRITE_FONT_SIMULATIONS_NONE, &font_face); + assert(error == S_OK); + + // DWrite Rendering Params + IDWriteRenderingParams *base_rendering_params = 0; + error = dwrite_factory->CreateRenderingParams(&base_rendering_params); + assert(error == S_OK); + + IDWriteRenderingParams *rendering_params = 0; + { + FLOAT gamma = 1.f; + //FLOAT gamma = base_rendering_params->GetGamma(); + FLOAT enhanced_contrast = base_rendering_params->GetEnhancedContrast(); + FLOAT clear_type_level = base_rendering_params->GetClearTypeLevel(); + error = dwrite_factory->CreateCustomRenderingParams( + gamma, + enhanced_contrast, + clear_type_level, + DWRITE_PIXEL_GEOMETRY_RGB, + DWRITE_RENDERING_MODE_NATURAL, + &rendering_params); + assert(error == S_OK); + } + + // DWrite GDI Interop + IDWriteGdiInterop *dwrite_gdi_interop = 0; + error = dwrite_factory->GetGdiInterop(&dwrite_gdi_interop); + assert(error == S_OK); + + // DWrite Bitmap Render Target + IDWriteBitmapRenderTarget *render_target = 0; + error = dwrite_gdi_interop->CreateBitmapRenderTarget(0, raster_target_w, raster_target_h, &render_target); + assert(error == S_OK); + + // Clear the Render Target + HDC dc = render_target->GetMemoryDC(); + + { + HGDIOBJ original = SelectObject(dc, GetStockObject(DC_PEN)); + SetDCPenColor(dc, back_color); + SelectObject(dc, GetStockObject(DC_BRUSH)); + SetDCBrushColor(dc, back_color); + Rectangle(dc, 0, 0, raster_target_w, raster_target_h); + SelectObject(dc, original); + } + + // Find the glyph index for the codepoint we want to render + uint16_t index = 0; + error = font_face->GetGlyphIndices(&codepoint, 1, &index); + assert(error == S_OK); + + // Render the glyph + DWRITE_GLYPH_RUN glyph_run = {0}; + glyph_run.fontFace = font_face; + glyph_run.fontEmSize = point_size*96.f/72.f; + glyph_run.glyphCount = 1; + glyph_run.glyphIndices = &index; + RECT bounding_box = {0}; + error = render_target->DrawGlyphRun(raster_target_x, raster_target_y, DWRITE_MEASURING_MODE_NATURAL, &glyph_run, rendering_params, fore_color, &bounding_box); + assert(error == S_OK); + + // Get the Bitmap + HBITMAP bitmap = (HBITMAP)GetCurrentObject(dc, OBJ_BITMAP); + DIBSECTION dib = {0}; + GetObject(bitmap, sizeof(dib), &dib); + + // Save the Bitmap + { + uint8_t *in_data = (uint8_t*)dib.dsBm.bmBits; + int32_t in_pitch = dib.dsBm.bmWidthBytes; + int32_t width = raster_target_w; + int32_t height = raster_target_h; + char *file_name = test_output_file_name; + + void *memory = (void*)malloc(1 << 20); + assert(memory != 0); + void *ptr = memory; + int32_t out_pitch = (width*3) + 3; + out_pitch = out_pitch - (out_pitch%4); + + Header *header = (Header*)ptr; + ptr = header + 1; + Info_Header *info_header = (Info_Header*)ptr; + ptr = info_header + 1; + uint8_t *out_data = (uint8_t*)ptr; + ptr = out_data + out_pitch*height; + + header->sig[0] = 'B'; + header->sig[1] = 'M'; + header->file_size = (uint8_t*)ptr - (uint8_t*)memory; + header->reserved = 0; + header->data_offset = out_data - (uint8_t*)memory; + info_header->size = sizeof(*info_header); + info_header->width = width; + info_header->height = height; + info_header->planes = 1; + info_header->bits_per_pixel = 24; + info_header->compression = 0; + info_header->image_size = 0; + info_header->x_pixels_per_meter = 0; + info_header->y_pixels_per_meter = 0; + info_header->colors_used = 0; + info_header->important_colors = 0; + + uint8_t *in_line = (uint8_t*)in_data; + uint8_t *out_line = out_data + out_pitch*(height - 1); + for (int32_t y = 0; y < height; y += 1){ + uint8_t *in_pixel = in_line; + uint8_t *out_pixel = out_line; + for (int32_t x = 0; x < width; x += 1){ + out_pixel[0] = in_pixel[0]; + out_pixel[1] = in_pixel[1]; + out_pixel[2] = in_pixel[2]; + in_pixel += 4; + out_pixel += 3; + } + in_line += in_pitch; + out_line -= out_pitch; + } + + FILE *out = fopen(file_name, "wb"); + assert(out != 0); + fwrite(memory, 1, header->file_size, out); + fclose(out); + } + + return(0); +} + diff --git a/project.4coder b/project.4coder new file mode 100644 index 0000000..10b9f6e --- /dev/null +++ b/project.4coder @@ -0,0 +1,31 @@ +version(1); +project_name = "direct_write"; +patterns = { +"*.c", +"*.cpp", +"*.h", +"*.m", +"*.bat", +"*.sh", +"*.4coder", +}; +blacklist_patterns = { +".*", +}; +load_paths_base = { + { ".", .relative = true, .recursive = true, }, +}; +load_paths = { + { load_paths_base, .os = "win", }, +}; + +command_list = { + { .name = "build", + .out = "*compilation*", .footer_panel = true, .save_dirty_files = true, + .cmd = { { "build_examples.bat" , .os = "win" }, }, }, + { .name = "run", + .out = "*run*", .footer_panel = false, .save_dirty_files = false, + .cmd = { { "build\\rasterize", .os = "win" }, }, }, +}; +fkey_command[1] = "build"; +fkey_command[2] = "run";