Thursday, December 31, 2020

Wrapping C API with C++ Part 2

In my last post I talked about adding RAII to a C API by wrapping it using guard objects. In this post I'm going to talk about dealing with an API that does not maintain its invariant. The API I'm using is fortunately (or unfortunately) the same SDL2 API from the last post, and the same SDL_Renderer management API. I found the same problem as discussed in this Stack Overflow question. The root of the problem are a pair of functions SDL_SetRenderTarget and SDL_GetRenderTarget. These are, it appears, intended to function similarly to SDL_RenderSetClipRect
and SDL_RenderGetClipRect. The Get version should return the same value as the Set version; but does not. The reason is in one of the answeres to the Stack Overflow question. The bug manifests in that, unless you save the Render Target yourself under the correct circumstances the program will not behave as intended.

There are at least two ways to solve this:

  1. I could modify my Renderer class to provide storage for the last Render Target set and modify RenderTargetGuard to work with that storage instead.
  2. I could use the C++ Standard Library container std::stack to implement a stack of past Render Targets.

I chose option 2. If my code was going to be creating and destroying many Renderers in the course of operation option 1 would have been better. But that is not the case; only a small number of Renderers are created at start up and used  throughout the program. Option 2 makes the Renderer class more complex but also lends itself to adding convenience functions to bring more of the functions of the SDL_Renderer API within the scope of idiomatic C++. Here is my new Renderer class (at the time this is written):

/**
* @class Renderer
* @brief Written as a workaround for an issue in the SDL2 Library.
* @details https://stackoverflow.com/questions/50415099/sdl-setrendertarget-doesnt-set-the-tartget
*/
class Renderer {
protected:
friend class RenderTargetGuard;

/**
* @brief A functor to destroy an SDL_Renderer
*/
class RendererDestroy {
public:
void operator()(SDL_Renderer *sdlRenderer) {
SDL_DestroyRenderer(sdlRenderer);
}
};

using RendererPtr = std::unique_ptr<SDL_Renderer, RendererDestroy>; ///< An SDL_Renderer unique pointer
RendererPtr mRenderer{}; ///< The Renderer.

std::stack<SDL_Texture*> mTargetTextureStack{}; ///< The stack of render targets

/**
* @brief Pop a render target off the stack. If there are none, set the default render target.
* @details The top of the stack is the current render target, unless the default is current in which
* case the stack will be empty. If the stack has 1 or 0 render targets on it, the renderer is set
* to the default target.
* @return The return status of SDL_SetRenderTarget
*/
[[nodiscard]] int popRenderTarget() {
if (mTargetTextureStack.empty())
return SDL_SetRenderTarget(mRenderer.get(), nullptr);
else {
mTargetTextureStack.pop();
if (mTargetTextureStack.empty())
return SDL_SetRenderTarget(mRenderer.get(), nullptr);
else
return SDL_SetRenderTarget(mRenderer.get(), mTargetTextureStack.top());
}
}

/**
* @brief Set a new render target, and push it onto the stack.
* @param texture The new render target.
* @return The return status of SDL_SetRenderTarget
*/
[[nodiscard]] int pushRenderTarget(sdl::Texture &texture) {
mTargetTextureStack.push(texture.get());
return SDL_SetRenderTarget(mRenderer.get(), texture.get());
}

/**
* @brief Set the render target to the default, and push it onto the stack.
* @return The return status of SDL_SetRenderTarget
*/
[[nodiscard]] int pushRenderTarget() {
mTargetTextureStack.push(nullptr);
return SDL_SetRenderTarget(mRenderer.get(), nullptr);
}

public:
/**
* Construct an empty renderer.
*/
Renderer() = default;

/**
* Construct a renderer associated with a Window.
* @param window The associated Window.
* @param index The index argument to SDL_CreateRenderer.
* @param flags The flags argument to SDL_CreateRenderer.
*/
Renderer(Window& window, int index, Uint32 flags);

/**
* @brief Move assignment operator.
* @param renderer The renderer to assign, this becomes empty after the assignment.
* @return A reference to this renderer.
*/
Renderer& operator=(Renderer&& renderer) = default;

/**
* @brief Test the renderer.
* @return false if the renderer is empty, true if it is valid.
*/
explicit operator bool () const noexcept { return mRenderer.operator bool(); }

/**
* @brief The the underlying SDL_Renderer* for use with the SDL2 API.
* @return An SDL_Renderer*
*/
[[nodiscard]] auto get() const { return mRenderer.get(); }

/**
* @brief Set the SDL_BlendMode on the renderer.
* @param blendMode
*/
void setDrawBlendMode(SDL_BlendMode blendMode) {
SDL_SetRenderDrawBlendMode(mRenderer.get(), blendMode);
}

/**
* @brief Create a Texture with the given size.
* @param size The Texture size.
* @return a new Texture object.
*/
Texture createTexture(SizeInt size);

/**
* @brief Copy source Texture to destination Texture and set the BlendMode on the destination Texture.
* @details The function uses RenderTargetGuard to temporarily set the render Target to the destination,
* calls SDL_RenderCopy to copy the texture, and sets the BlendMode on the destination texture to
* SDL_BLENDMODE_BLEND.
* @param source
* @param destination
*/
void copyFullTexture(sdl::Texture &source, sdl::Texture &destination);

/**
* @brief Calls SDL_RenderClear on the renderer.
* @return The return status from SDL_RenderClear.
*/
int renderClear() { return SDL_RenderClear(mRenderer.get()); }

/**
* @brief Calls SDL_RenderPresent on the renderer.
*/
void renderPresent() { SDL_RenderPresent(mRenderer.get()); }

/**
* @brief Calls SDL_RenderCopy to copy the source texture to the current render target.
* @details SDL_RenderCopy is called with nullptr for srcrect and dstrect.
* @param texture The texture to copy.
* @return The return status of SDL_RenderCopy.
*/
int renderCopy(Texture &texture) { return SDL_RenderCopy(mRenderer.get(), texture.get(), nullptr, nullptr); }

/**
* @brief Calls SDL_RenderFillRect after setting the RenderDrawColor to color.
* @details The existing RenderDrawColor is saved and restored.
* @param rectangle The rectangle to fill.
* @param color The fill color.
* @return The return status of SDL_RenderFillRect.
*/
int fillRect(RectangleInt rectangle, Color color);
};

And here is the new RenderTargetGuard class:

/**
* @class RenderTargetGuardException
* @brief Thrown by RenderTargetGuard on errors.
*/
class RenderTargetGuardException : public SdlRuntimeException {
public:
explicit RenderTargetGuardException(const std::string &what_arg) : SdlRuntimeException(what_arg) {}
};

/**
* @class RenderTargetGuard
* @brief Store the current render target replacing it with a new render target. When the object is
* destroyed (by going out of scope) the old render target is restored.
*/
class RenderTargetGuard {
protected:
Renderer &mRenderer;
bool popped{false};
int status{0};

public:
RenderTargetGuard() = delete;
RenderTargetGuard(const RenderTargetGuard&) = delete;

/**
* @brief Set the old render target back on the renderer when destroyed.
*/
~RenderTargetGuard() noexcept(false) {
if (!popped) {
status = mRenderer.popRenderTarget();
if (status)
throw RenderTargetGuardException(util::StringCompositor("Call to SDL_SetRenderTarget failed:",
SDL_GetError()));
}
}

/**
* @brief Test the status of the RenderTargetGuard.
* @details The status is good (true is returned) if the status value returned from the last
* operation on the Renderer object returned 0.
* @return True if the last operation succeeded.
*/
explicit operator bool () const noexcept { return status == 0; }

/**
* @brief Constructor
* @param renderer The renderer which render target will be managed.
* @param texture The texture which will become the new render target.
*/
RenderTargetGuard(Renderer &renderer, Texture &texture) : mRenderer(renderer) {
status = mRenderer.pushRenderTarget(texture);
}

/**
* @brief Clear the render target so rendering will be sent to the screen backing buffer.
*/
void clear() {
status = mRenderer.popRenderTarget();
popped = true;
}

/**
* @brief Set a new render target without pushing it on the stack.
* @details This may be used when a number of render target changes are needed in a context block.
* A RenderTargetGuard is created, calls to setRenderTarget are used to manipulate the render target.
* When the RenderTargetGuard goes out of scope the original render target is restored.
* @param texture
* @return
*/
int setRenderTarget(Texture &texture) {
status = SDL_SetRenderTarget(mRenderer.get(), texture.get());
return status;
}
};

Wednesday, December 9, 2020

Wrapping C APIs with C++

If you write in C++ it is nice not to have to switch mental gears back to C, but sometimes it is inevitable because many of the libraries and interfaces we use are written in C. There are many good reasons for this that you can find elsewhere.

I'm currently writing a GUI library targeting the Raspberry Pi frame buffer (no desktop GUI interface loaded) with touchscreen or mouse as the gesture interface. I am building this on top of SDL2, which uses a C interface style, so that's where my examples come from.

The first thing that normally comes up when talking about wrapping C APIs in C++ is how to deal with bear pointers. Of course the thing to do is use a smart pointer, either a std::unique_ptr or a std::shared_ptr depending on the context. Usually for a pointer that the calling program is responsible for deleting a std::unique_ptr is best, but give it some thought. One such bare pointer in the SDL2 libarary is the SDL_Renderer which is created by SDL_CreateRenderer and destroyed by SDL_DestroyRenderer. I usually create a function class to destroy the pointer, and declare a type alias for the std::unique_ptr:

/**
* @brief A functor to destroy an SDL_Renderer
*/
class RendererDestroy {
public:
void operator()(SDL_Renderer *sdlRenderer) {
SDL_DestroyRenderer(sdlRenderer);
}
};
///< An SDL_Renderer unique pointer
using Renderer = std::unique_ptr<SDL_Renderer, RendererDestroy>;

Once this is done you can use these in code and know that when the Renderer goes out of scope, the pointer will be properly destroyed and can't be used again. When you need to pass the actual C style bare pointer to an API call, simply use the get() method of the std::unique_ptr.

Renderer renderer = SDL_CreateRenderer(mSdlWindow.get(), -1,
SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
if (renderer) {
SDL_SetRenderDrawBlendMode(renderer.get(), SDL_BLENDMODE_BLEND);
} else {
mErrorCode = RoseErrorCode::SDL_RENDERER_CREATE;
std::cerr << "Could not create renderer: " << SDL_GetError() << '\n';
}

But that isn't all you can do to wrap up the API. Sometimes you wish to set a property on a pointer, use it within a scope and then remove it restoring the original state. This can involve a bunch of tedious temporary variables and ensuring calls with the correct variables are made in the correct sequence. Take this guard class for an SDL Clipping Rectangle for example:

/**
* @class ClipRectangleGuard
* @brief Store the current clip rectangle replacing it with a new clip rectangle. When the object is
* destroyed (by going out of scope) the old clip rectangle is set.
*/
class ClipRectangleGuard {
protected:
Renderer &mRenderer; ///< The renderer to which the clip rectangles are set.
SDL_Rect mOldClip{}; ///< The old clip rectangle

public:
ClipRectangleGuard() = delete;
ClipRectangleGuard(ClipRectangleGuard&) = delete;

/**
* @brief Set the old clip rectangle back on the renderer when destroyed.
*/
~ClipRectangleGuard() {
SDL_RenderSetClipRect(mRenderer.get(), &mOldClip);
}

/**
* @brief Constructor. Store the current clip rectangle and set the new one.
* @param renderer The renderer to set the clip rectangles on.
* @param clip The new clip rectangle.
*/
ClipRectangleGuard(Renderer& renderer, const SDL_Rect &clip) : mRenderer(renderer) {
SDL_RenderGetClipRect(mRenderer.get(), &mOldClip);
SDL_RenderSetClipRect(mRenderer.get(), &clip);
}

/**
* @brief Constructor. Store the current clip rectangle and set the new one.
* @param renderer The renderer to set the clip rectangles on.
* @param x X co-ordinate of the new clip rectangle.
* @param y Y co-ordinate of the new clip rectangle.
* @param w Width of the new clip rectangle.
* @param h Height of the new clip rectangle.
*/
ClipRectangleGuard(Renderer& renderer, int x, int y, int w, int h)
: ClipRectangleGuard(renderer, SDL_Rect{x,y,w,h}) {}
};

This is used to set a new Clipping Rectangle, while saving the current value, for the remainder of the scope. The original value is restored as soon as the guard is destroyed.

void ScrollArea::draw(Renderer &renderer, SizeInt size, PositionInt position) {
auto screenPos = getScreenPosition();
ClipRectangleGuard clipRectangleGuard(renderer, screenPos.x(), screenPos.y(), mSize.width(), mSize.height());
Container::draw(renderer, SizeInt{}, mScrollOffset);
}

These are two steps that can help to make calling C style APIs much less error prone by getting the details correct once, and then using C++ idiom from that point on.