How to get a part of texture in SDL2? - c++

I try to do it by using SDL_RenderCopy()
But I just get a black box.
This is my code.
static SDL_Texture* GetAreaTextrue(SDL_Rect rect, SDL_Renderer* renderer, SDL_Texture* source)
{
SDL_Texture* result = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect.w, rect.h);
SDL_SetRenderTarget(renderer, result);
SDL_RenderCopy(renderer, source, &rect, NULL);
SDL_RenderPresent(renderer);
return result;
}
What is the proper operation?

EDIT:
What you want to do here is render a part of the texture to the screen.
There is a way to do this by using SDL_RenderCopy, but by doing this you just "take" the part you want from your texture and "slap" it onto your screen.
What you want (by my understanding) is to take a part of a texture and save it into another texture variable that afterwards can be rendered to the screen.
The first solution to the problem goes like this:
// load your image in a SDL_Texture variable (myTexture for example)
// if the image is (256,128) and you want to render only the first half
// you need a rectangle of the same size as that part of the image
SDL_Rect imgPartRect;
imgPartRect.x = 0;
imgPartRect.y = 0;
imgPartRect.w = 32;
imgPartRect.h = 32;
// and the program loop should have a draw block looking like this:
SDL_SetRenderDrawColor( renderer, 0x00, 0x00, 0x00, 0xFF );
SDL_RenderClear( renderer );
SDL_RenderCopy( renderer, myTexture, &imgPartRect, NULL );
SDL_RenderPresent( renderer );
The approach you are trying to use has an intermediate texture that you render on and afterwards you render that texture to the screen. The problem here is that you set the renderer to draw on the texture you just created but you never reset your renderer to use the default target (the screen).
As you can see in the SDL documentation here the second parameter receives the texture that you want your renerer to draw on, or NULL if you want to reset it to the default target (the screen).
int SDL_SetRenderTarget(SDL_Renderer* renderer, SDL_Texture* texture)
Where:
renderer
the rendering context
texture
the targeted texture, which must be created with the SDL_TEXTUREACCESS_TARGET flag, or NULL for the default render target
Lets use the same example as before:
// first off let's build the function that you speak of
SDL_Texture* GetAreaTextrue(SDL_Rect rect, SDL_Renderer* renderer, SDL_Texture* source)
{
SDL_Texture* result = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect.w, rect.h);
SDL_SetRenderTarget(renderer, result);
SDL_RenderCopy(renderer, source, &rect, NULL);
// the folowing line should reset the target to default(the screen)
SDL_SetRenderTarget(renderer, NULL);
// I also removed the RenderPresent funcion as it is not needed here
return result;
}
// load your image in a SDL_Texture variable (myTexture for example)
// if the image is (256,128) and you want to render only the first half
// you need a rectangle of the same size as that part of the image
SDL_Rect imgPartRect;
imgPartRect.x = 0;
imgPartRect.y = 0;
imgPartRect.w = 32;
imgPartRect.h = 32;
// now we use the function from above to build another texture
SDL_Texture* myTexturePart = GetAreaTextrue( imgPartRect, renderer, myTexture );
// and the program loop should have a draw block looking like this:
SDL_SetRenderDrawColor( renderer, 0x00, 0x00, 0x00, 0xFF );
SDL_RenderClear( renderer );
// here we render the whole newly created texture as it contains only a part of myTexture
SDL_RenderCopy( renderer, myTexturePart, NULL, NULL );
SDL_RenderPresent( renderer );
I don't know what you want to do, but I highly recommend the first way though.

Related

Textures created with SDL_CreateTexture don't appear to support transparency

I want to copy multiple surfaces (created with TTF_*) to a single texture, and I can't seem to get that resulting texture to render onto the window with transparency handled correctly.
static void example(void) {
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
TTF_Init();
SDL_Window* w = SDL_CreateWindow("", 0, 0, 200, 200, 0);
SDL_Renderer* r = SDL_CreateRenderer(w, -1, 0);
TTF_Font* f = TTF_OpenFont(MY_FONT, 100);
SDL_Color c = {.r = 0, .g = 255, .b = 0, .a = 255};
SDL_Surface* s = TTF_RenderGlyph32_Blended(f, 'A', c);
SDL_Texture* t = SDL_CreateTextureFromSurface(r, s);
#ifdef RENDER_COPY
SDL_Texture* t2 = SDL_CreateTexture(
r,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_TARGET,
s->w,
s->h);
SDL_SetRenderTarget(r, t2);
SDL_RenderCopy(r, t, NULL, NULL);
SDL_SetRenderTarget(r, NULL);
t = t2;
#endif
#ifdef RENDER_MEMCPY
SDL_Texture* t2 = SDL_CreateTexture(
r,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
s->w,
s->h);
SDL_Surface* s2;
sdl_try(SDL_LockTextureToSurface(t2, NULL, &s2));
memcpy(s2->pixels, s->pixels, s->w * s->h * sizeof(SDL_Color));
SDL_UnlockTexture(t2);
t = t2;
#endif
#ifdef RENDER_BLEND
SDL_SetTextureBlendMode(t, SDL_BLENDMODE_BLEND);
#endif
SDL_SetRenderDrawColor(r, 255, 255, 255, 255);
SDL_RenderClear(r);
SDL_Rect rect = {.x = 0, .y = 0};
SDL_QueryTexture(t, NULL, NULL, &rect.w, &rect.h);
SDL_RenderCopy(r, t, &rect, &rect);
SDL_RenderPresent(r);
SDL_Event event;
do { SDL_WaitEvent(&event); } while (event.type != SDL_KEYDOWN);
}
Without RENDER_COPY, I get a texture (created via SDL_CreateTextureFromSurface) that blends correctly onto a render target (this is what I want, but with multiple surfaces combined into one texture.)
With RENDER_COPY (i.e. a second texture is created and then copied onto) the background of the texture is black. This is a contrived example since there is only one surface being copied, but I want to copy multiple surfaces to t2.)
With RENDER_BLEND, the black is mostly gone but it's as if the texture was blended onto a black background.
Is there a way to create a texture that can be set completely transparent instead of a solid color? I've also tried to set the pixels directly (RENDER_MEMCPY) but that just ends up being a solid color as it appears the alpha in each pixel is ignored:
SDL version is 2.0.20.
Figured it out. When doing SDL_RenderCopy from the first texture to the second, the blend mode on the first texture should be set to none:
SDL_SetTextureBlendMode(t, SDL_BLENDMODE_NONE);
Now when the second texture is copied (with SDL_BLENDMODE_BLEND) the edges don't have the black artifacts.

SDL2 program only works if Renderer is created with SDL_RENDERER_SOFTWARE

I've written a program using C++ and SDL2 which:
creates a window
gets the window's surface
creates a renderer for the window
renders some filled rectangles onto the window
creates a texture from the window's surface
clears the screen
renders some filled circles onto the window
creates a second texture from the window's surface
enters an event loop, where every time a key is pressed:
if circles are currently being displayed, SDL_RenderCopy() is used to copy the squares texture to the window.
else if squares are currently being displayed, the circles texture is copied to the window.
The program works perfectly if the renderer is created with the SDL_RENDERER_SOFTWARE flag.
If the renderer is created with the SDL_RENDERER_ACCELERATED flag, I find that while I can render direct to the screen, if I create a couple of different textures from the window's surface and then try to copy them back to the window using SDL_RenderCopy(); all I see is a black window.
I can't find any failing SDL calls.
I've wondered whether there might be some incompatibility between the texture format and the renderer - but I'm not sure how to follow this up.
Any help or suggestions?
My environment is :
Windows 10
Visual Studio Community 2015
SDL2 version 2.0.4
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Additional information and source code :
I've added cut down source code to demonstrate the problem below.
Note, to keep down the size, I've removed all of the error checking and consolidated the relevant code into a single main function.
What I get is that the program works for me as expected if I uncomment line 40 so that I am calling SDL_CreateRenderer with the SDL_RENDERER_SOFTWARE flag.
If I un-comment any of the other SDL_CreateRenderer lines instead (line 41-43 : to use hardware acceleration), I see the red and blue squares when they are initially rendered to screen.
But as I press keys, instead of the window flicking between red and blue squares, I'm looking at a black window.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include <SDL.h>
#include <SDL_image.h>
#include <stdio.h>
#include <string>
//Screen dimension constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;
int main(int argc, char* args[])
{
//The window we'll be rendering to
SDL_Window* gWindow = NULL;
//The surface contained by the window
SDL_Surface* gScreenSurface = NULL;
//And two textures, one for a red square, on for a blue square
SDL_Texture* texture_red = NULL;
SDL_Texture* texture_blue = NULL;
//The window renderer
SDL_Renderer* gRenderer = NULL;
//Initialize SDL
SDL_Init(SDL_INIT_VIDEO);
//Create window
gWindow = SDL_CreateWindow("SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
//Get the screen surface
gScreenSurface = SDL_GetWindowSurface(gWindow);
//Create renderer for window
gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_SOFTWARE);
//gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED);
//gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_PRESENTVSYNC);
//gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
/*###########################################################################################
# I can not figure out how to make this program work with hardware acceleration. It works #
# fine when I define the renderer using SDL_RENDERER_SOFTWARE, but doesn't display anything #
# if I define the renerer using the SDL_RENDERER_ACCELERATED flag #
###########################################################################################*/
//Initialize renderer color
SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
//Clear screen
SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(gRenderer);
//Render red filled quad
SDL_Rect fillRect = { 100, 75, 100, 100 };
SDL_SetRenderDrawColor(gRenderer, 0xFF, 0x00, 0x00, 0xFF);
SDL_RenderFillRect(gRenderer, &fillRect);
//Update the rendered image on screen
SDL_RenderPresent(gRenderer);
//Pause long enough to see it
SDL_Delay(200);
//Create texture_red texture from the screen surface
texture_red = SDL_CreateTextureFromSurface(gRenderer, gScreenSurface);
//Clear screen
SDL_SetRenderDrawColor(gRenderer, 0xFF, 0xFF, 0xFF, 0xFF);
SDL_RenderClear(gRenderer);
//Render blue filled quad
fillRect = { 225, 250, 100, 100 };
SDL_SetRenderDrawColor(gRenderer, 0x00, 0x00, 0xFF, 0xFF);
SDL_RenderFillRect(gRenderer, &fillRect);
//Update the rendered image on screen
SDL_RenderPresent(gRenderer);
//Pause long enough to see it
SDL_Delay(200);
//Create texture_red texture from the screen surface
texture_blue = SDL_CreateTextureFromSurface(gRenderer, gScreenSurface);
//Main loop flag
bool quit = false;
//Flag to keep track of which colour we're currently looking at
bool blue = true;
//Event handler
SDL_Event e;
//While application is running
while (!quit)
{
//Handle events on queue
while (SDL_PollEvent(&e) != 0)
{
//User requests quit
if (e.type == SDL_QUIT)
{
quit = true;
}
//User presses a key
else if (e.type == SDL_KEYDOWN)
{
//Select surfaces based on key press
switch (e.key.keysym.sym)
{
case SDLK_ESCAPE:
quit = true;
break;
default:
if (blue)
{
//Copy surface used to store red image onto the screen surface
SDL_RenderCopy(gRenderer, texture_red, NULL, NULL);
//Update current colour flag
blue = false;
}
else
{
//Copy surface used to store blue image onto the screen surface
SDL_RenderCopy(gRenderer, texture_blue, NULL, NULL);
//Update current colour flag
blue = true;
}
//Update the screen with recent render activity
SDL_RenderPresent(gRenderer);
break;
}
}
}
}
//Deallocate surfaces
SDL_FreeSurface(gScreenSurface);
//Destroy window
SDL_DestroyWindow(gWindow);
gWindow = NULL;
//Quit SDL subsystems
SDL_Quit();
return 0;
}
The SDL_GetWindowSurface() header comment specifically prohibits using it with the SDL_Renderer functionality:
/**
* \brief Get the SDL surface associated with the window.
*
* \return The window's framebuffer surface, or NULL on error.
*
* A new surface will be created with the optimal format for the window,
* if necessary. This surface will be freed when the window is destroyed.
*
* \note You may not combine this with 3D or the rendering API on this window.
*
* \sa SDL_UpdateWindowSurface()
* \sa SDL_UpdateWindowSurfaceRects()
*/
extern DECLSPEC SDL_Surface * SDLCALL SDL_GetWindowSurface(SDL_Window * window);
Use SDL_RENDERER_TARGETTEXTURE & SDL_SetRenderTarget() if you want to capture SDL_Renderer output.
You can use SDL_GetRendererInfo() to query a compatible texture format. Or just blast ahead with SDL_PIXELFORMAT_ARGB8888 like testrendertarget.c does and hope for the best :)
Try adding SDL_RENDERER_PRESENTVSYNC to your renderer's flag (Proposed by someone here).
C++ seems to be the programming language you're using but for those looking for a fully working SDL2.0.8-based C program that does not use GetWindowSurface(), please take a look here:
Mainly,
initWindowAndRenderer
drawText
drawImage
drawVideo
setBgColor
drawVideo
...
Here is an extract with error-checking removed:
// Create a window usable with OpenGL context
window = SDL_CreateWindow("Title", x, y, w, h, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN);
// Select render driver
// - A render driver that supports HW acceleration is used when available
// - Otherwise a render driver supporting software fallback is selected
SDL_RendererInfo renderDriverInfo;
uint32_t rendererFlags = SDL_RENDERER_TARGETTEXTURE;
int32_t nbRenderDrivers = SDL_GetNumRenderDrivers(), index = 0;
while (index < nbRenderDrivers)
{
if (SDL_GetRenderDriverInfo(index, &renderDriverInfo) == 0)
{
if (((renderDriverInfo.flags & rendererFlags) == rendererFlags)
&& ((renderDriverInfo.flags & SDL_RENDERER_ACCELERATED) == SDL_RENDERER_ACCELERATED))
{
// Using render driver with HW acceleration
rendererFlags |= SDL_RENDERER_ACCELERATED;
SDL_SetHint(SDL_HINT_RENDER_DRIVER, renderDriverInfo.name);
break;
}
}
++index;
}
if (index == nbRenderDrivers)
{
// Let SDL use the first render driver supporting software fallback
rendererFlags |= SDL_RENDERER_SOFTWARE;
index = -1;
}
// Create renderer
renderer = SDL_CreateRenderer(window, index, rendererFlags);

SDL not rendering pixels

I'm trying to change the background color of my window in SDL with pixel level access (I'm making a particle explosion), but the background is staying white.
First I tried using memset to set the memory of the pixels to 0x00 to change the color to black (I used memset because I am following a tutorial that used it)
memset(buffer, 0x00, SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(Uint32));
Since that didn't work I looked up how to change the draw color of the renderer and came up with:
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 255);
Here is my Texture, Renderer, and Buffer code, and how I set up the window (SCREEN_WIDTH and SCREEN_HEIGHT are const ints set to 800 and 600 respectively):
SDL_Window* window = SDL_CreateWindow("Fire Particle Explosion",
SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC);
SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STATIC, SCREEN_WIDTH, SCREEN_HEIGHT);
Uint32* buffer = new Uint32[SCREEN_WIDTH * SCREEN_HEIGHT];
SDL_UpdateTexture(texture, NULL, buffer, SCREEN_WIDTH * sizeof(Uint32));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, NULL, NULL);
SDL_RenderPresent(renderer);
How would I properly change the background pixel by pixel, and why do neither of the ways I attempted work?
You don't need to manipulate a buffer in memory to achieve that, and you may not need a texture. The recommended way is to use the "2D Accelerated Rendering" functions in SDL2.
First of all, start without the "SDL_RENDERER_PRESENTVSYNC" parameter, use '0' instead. On Linux, some old graphics drivers produce flickering with that flag.
This should erase the screen in black:
SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 255);
SDL_RenderClear(renderer);
I strongly advise you to use SDL_RenderDrawPoint() in order to update the screen pixel by pixel. It is hardware accelerated.
Basically, here is what happens in your game loop:
- set the color to black, then erase the screen with SDL_RenderClear()
- for each point to draw, first set the draw color, then draw the point
- use SDL_RenderPresent()
I have written a small example of SDL2 code in C for making particle explosions: drawpoints

SDL Texture transparent background

This is probably rather simple problem, but after an hour of searching and trying I still didn't manage to solve it.
I have two png files. One is a background image and second is foreground. The foreground has an alpha channel. I want to display foreground on top of background.
I'm loading foreground using:
SDL_Surface *clip = SDL_CreateRGBSurface(0, SCREEN_WIDTH, SCREEN_HEIGHT, 32, 0, 0, 0, 0xff);
SDL_Rect rect = { x, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
SDL_BlitSurface(map, &rect, clip, NULL);
*block = SDL_CreateTextureFromSurface(gRenderer, clip);
Where map is some SDL_Surface.
I'm loadin backgroun using:
SDL_Surface* loadedSurface = IMG_Load(path);
//Create texture from surface pixels
SDL_Texture* newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);
SDL_FreeSurface(loadedSurface);
Then I trying to connect them:
SDL_RenderCopy(gRenderer, background, NULL, &cur);
SDL_RenderCopy(gRenderer, map, NULL, &cur);
But it results in foreground image with black background. What am i doing wrong?
You should add these 2 lines,
Uint32 colorkey = SDL_MapRGB(loadedSurface->format, 0, 0, 0);
SDL_SetColorKey(loadedSurface, SDL_TRUE, colorkey);
before this line in your code
SDL_Texture* newTexture = SDL_CreateTextureFromSurface(gRenderer, loadedSurface);

Enable AntiAliasing in Direct3D9 (MultiSample Render Target)

I am trying to enable AA in a D3D9 application, but am not sure how to set up the surfaces correctly. So far, I have:
IDirect3DDevice9* m_pd3dDevice;
IDirect3DSurface9* screen;
IDirect3DSurface9* msaasurf;
D3DPRESENT_PARAMETERS m_presentationParameters;
Initialization:
m_presentationParameters.Windowed = TRUE;
m_presentationParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
m_presentationParameters.MultiSampleType = D3DMULTISAMPLE_2_SAMPLES;
m_presentationParameters.MultiSampleQuality = 0;
m_presentationParameters.BackBufferFormat = D3DFMT_UNKNOWN;
m_presentationParameters.EnableAutoDepthStencil = TRUE;
m_presentationParameters.AutoDepthStencilFormat = D3DFMT_D16;
m_presentationParameters.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
// create d3d device
m_pD3D->CreateDevice(
D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING,
&m_presentationParameters, &m_pd3dDevice
)
// save screen surface
m_pd3dDevice->GetRenderTarget(0, &screen);
D3DSURFACE_DESC desc;
screen->GetDesc(&desc);
// Create multisample render target
m_pd3dDevice->CreateRenderTarget(
800, 600,
D3DFMT_A8R8G8B8,
desc.MultiSampleType, desc.MultiSampleQuality,
false,
&msaasurf,
NULL
);
And then, for each frame:
// render to multisample surface
m_pd3dDevice->SetRenderTarget(0, msaasurf);
m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB( 0, 0, 0 ), 1.0f, 0 );
m_pd3dDevice->BeginScene();
// render stuff here
m_pd3dDevice->EndScene();
m_pd3dDevice->SetRenderTarget(0, screen);
// get back buffer
IDirect3DSurface9* backBuffer = NULL;
m_pd3dDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer);
// copy rendertarget to backbuffer
m_pd3dDevice->StretchRect(msaasurf, NULL, backBuffer, NULL, D3DTEXF_NONE);
backBuffer->Release();
// Present the backbuffer contents to the display
m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
However, nothing is appearing on my screen (all black). No errors are occuring (I check the return value of all d3d calls). What am I doing wrong?
You don't need the extra surface, you can render directly to the multisampled backbuffer. For me, the only reason to use StretchRect() like this is to get a non-multisampled copy of the scene for use with postprocessing (because multisampled render targets are bad textures, so you need the scene data in a resolved texture). If you want to do this, you don't need to specify multisampling for the backbuffer. A multisampled render target to render the scene to is sufficient then.