Refactor how render requests are generated.
Get the camera and drawmode from the bridges rather than recalculating it unecessarily. PiperOrigin-RevId: 911406470 Change-Id: Ia295846bb80105a2b0ad5da21912ca82b25e04bd
This commit is contained in:
committed by
Copybara-Service
parent
2b67fe3c3a
commit
2aa2131057
@@ -302,6 +302,29 @@ void ImguiBridge::PrepareRenderables(int count) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mjrScene* ImguiBridge::GetScene() const { return scene_.get(); }
|
||||||
|
|
||||||
|
mjrCamera ImguiBridge::GetCamera(int width, int height) const {
|
||||||
|
mjrCamera camera;
|
||||||
|
camera.orthographic = true;
|
||||||
|
camera.pos[0] = 0.0f;
|
||||||
|
camera.pos[1] = 0.0f;
|
||||||
|
camera.pos[2] = 1.0f;
|
||||||
|
camera.forward[0] = 0.0f;
|
||||||
|
camera.forward[1] = 0.0f;
|
||||||
|
camera.forward[2] = -1.0f;
|
||||||
|
camera.up[0] = 0.0f;
|
||||||
|
camera.up[1] = 1.0f;
|
||||||
|
camera.up[2] = 0.0f;
|
||||||
|
camera.frustum_top = 0.0f;
|
||||||
|
camera.frustum_near = 0.0f;
|
||||||
|
camera.frustum_far = 1.0f;
|
||||||
|
camera.frustum_center = width / 2.0f;
|
||||||
|
camera.frustum_width = width / 2.0f;
|
||||||
|
camera.frustum_bottom = height;
|
||||||
|
return camera;
|
||||||
|
}
|
||||||
|
|
||||||
static ImVec2 ClipSpaceToWindowCoordinates(float x, float y) {
|
static ImVec2 ClipSpaceToWindowCoordinates(float x, float y) {
|
||||||
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
|
const ImVec2& display_size = ImGui::GetIO().DisplaySize;
|
||||||
const float pos_x = display_size.x * ((x + 1) * 0.5f);
|
const float pos_x = display_size.x * ((x + 1) * 0.5f);
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ class ImguiBridge {
|
|||||||
void Update();
|
void Update();
|
||||||
|
|
||||||
// Returns the managed UX scene.
|
// Returns the managed UX scene.
|
||||||
mjrScene* GetScene() const { return scene_.get(); }
|
mjrScene* GetScene() const;
|
||||||
|
mjrCamera GetCamera(int width, int height) const;
|
||||||
|
|
||||||
// Uploads texture to be used with ImGui's Image and ImageButton functions.
|
// Uploads texture to be used with ImGui's Image and ImageButton functions.
|
||||||
uintptr_t UploadImage(uintptr_t tex_id, const uint8_t* pixels, int width,
|
uintptr_t UploadImage(uintptr_t tex_id, const uint8_t* pixels, int width,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
#include <math/mathfwd.h>
|
#include <math/mathfwd.h>
|
||||||
#include <math/vec4.h>
|
#include <math/vec4.h>
|
||||||
@@ -42,31 +41,6 @@ void MjrFilamentRenderer::Init(const mjModel* model) {
|
|||||||
scene_bridge_ = std::make_unique<SceneBridge>(filament_context_.get(), model);
|
scene_bridge_ = std::make_unique<SceneBridge>(filament_context_.get(), model);
|
||||||
imgui_bridge_ = std::make_unique<ImguiBridge>(filament_context_.get());
|
imgui_bridge_ = std::make_unique<ImguiBridge>(filament_context_.get());
|
||||||
scene_bridge_->SetDrawTextFunction(DrawTextAt);
|
scene_bridge_->SetDrawTextFunction(DrawTextAt);
|
||||||
|
|
||||||
mjr_defaultRenderRequest(&render_requests_[0]);
|
|
||||||
mjr_defaultRenderRequest(&render_requests_[1]);
|
|
||||||
|
|
||||||
render_requests_[0].scene = scene_bridge_->GetScene();
|
|
||||||
render_requests_[0].draw_mode = mjDRAW_MODE_COLOR;
|
|
||||||
|
|
||||||
render_requests_[1].scene = imgui_bridge_->GetScene();
|
|
||||||
render_requests_[1].draw_mode = mjDRAW_MODE_COLOR;
|
|
||||||
|
|
||||||
// The UX camera is a fixed orthographic camera. We only need to change the
|
|
||||||
// width/height based on the viewport per frame.
|
|
||||||
render_requests_[1].camera.orthographic = true;
|
|
||||||
render_requests_[1].camera.pos[0] = 0.0f;
|
|
||||||
render_requests_[1].camera.pos[1] = 0.0f;
|
|
||||||
render_requests_[1].camera.pos[2] = 1.0f;
|
|
||||||
render_requests_[1].camera.forward[0] = 0.0f;
|
|
||||||
render_requests_[1].camera.forward[1] = 0.0f;
|
|
||||||
render_requests_[1].camera.forward[2] = -1.0f;
|
|
||||||
render_requests_[1].camera.up[0] = 0.0f;
|
|
||||||
render_requests_[1].camera.up[1] = 1.0f;
|
|
||||||
render_requests_[1].camera.up[2] = 0.0f;
|
|
||||||
render_requests_[1].camera.frustum_top = 0.0f;
|
|
||||||
render_requests_[1].camera.frustum_near = 0.0f;
|
|
||||||
render_requests_[1].camera.frustum_far = 1.0f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MjrFilamentRenderer::Render(const mjrRect& viewport,
|
void MjrFilamentRenderer::Render(const mjrRect& viewport,
|
||||||
@@ -78,29 +52,22 @@ void MjrFilamentRenderer::Render(const mjrRect& viewport,
|
|||||||
imgui_bridge_->Update();
|
imgui_bridge_->Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scene->flags[mjRND_SEGMENT]) {
|
|
||||||
render_requests_[0].draw_mode = mjDRAW_MODE_SEGMENTATION;
|
|
||||||
} else if (scene->flags[mjRND_DEPTH]) {
|
|
||||||
render_requests_[0].draw_mode = mjDRAW_MODE_DEPTH;
|
|
||||||
} else {
|
|
||||||
render_requests_[0].draw_mode = mjDRAW_MODE_COLOR;
|
|
||||||
}
|
|
||||||
|
|
||||||
render_requests_[0].width = viewport.width;
|
|
||||||
render_requests_[0].height = viewport.height;
|
|
||||||
render_requests_[1].width = viewport.width;
|
|
||||||
render_requests_[1].height = viewport.height;
|
|
||||||
|
|
||||||
render_requests_[0].camera =
|
|
||||||
mjv_averageCamera(scene->camera, scene->camera + 1);
|
|
||||||
render_requests_[1].camera.frustum_center = viewport.width / 2.0f;
|
|
||||||
render_requests_[1].camera.frustum_width = viewport.width / 2.0f;
|
|
||||||
render_requests_[1].camera.frustum_bottom = viewport.height;
|
|
||||||
|
|
||||||
if (mode_ == FrameBufferMode::Window) {
|
if (mode_ == FrameBufferMode::Window) {
|
||||||
render_requests_[0].target = nullptr;
|
mjrRenderRequest reqs[2];
|
||||||
render_requests_[1].target = nullptr;
|
mjr_defaultRenderRequest(&reqs[0]);
|
||||||
filament_context_->Render(render_requests_);
|
reqs[0].scene = scene_bridge_->GetScene();
|
||||||
|
reqs[0].draw_mode = scene_bridge_->GetDrawMode();
|
||||||
|
reqs[0].camera = scene_bridge_->GetCamera();
|
||||||
|
reqs[0].width = viewport.width;
|
||||||
|
reqs[0].height = viewport.height;
|
||||||
|
|
||||||
|
mjr_defaultRenderRequest(&reqs[1]);
|
||||||
|
reqs[1].scene = imgui_bridge_->GetScene();
|
||||||
|
reqs[1].draw_mode = mjDRAW_MODE_COLOR;
|
||||||
|
reqs[1].camera = imgui_bridge_->GetCamera(viewport.width, viewport.height);
|
||||||
|
reqs[1].width = viewport.width;
|
||||||
|
reqs[1].height = viewport.height;
|
||||||
|
filament_context_->Render(reqs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,10 +93,21 @@ void MjrFilamentRenderer::ReadPixels(mjrRect viewport, unsigned char* rgb,
|
|||||||
mju_error("ReadPixels is only supported for offscreen rendering.");
|
mju_error("ReadPixels is only supported for offscreen rendering.");
|
||||||
}
|
}
|
||||||
|
|
||||||
render_requests_[0].width = viewport.width;
|
mjrRenderRequest reqs[2];
|
||||||
render_requests_[0].height = viewport.height;
|
mjr_defaultRenderRequest(&reqs[0]);
|
||||||
render_requests_[1].width = viewport.width;
|
|
||||||
render_requests_[1].height = viewport.height;
|
reqs[0].scene = scene_bridge_->GetScene();
|
||||||
|
reqs[0].draw_mode = scene_bridge_->GetDrawMode();
|
||||||
|
reqs[0].camera = scene_bridge_->GetCamera();
|
||||||
|
reqs[0].width = viewport.width;
|
||||||
|
reqs[0].height = viewport.height;
|
||||||
|
|
||||||
|
mjr_defaultRenderRequest(&reqs[1]);
|
||||||
|
reqs[1].scene = imgui_bridge_->GetScene();
|
||||||
|
reqs[1].draw_mode = mjDRAW_MODE_COLOR;
|
||||||
|
reqs[1].camera = imgui_bridge_->GetCamera(viewport.width, viewport.height);
|
||||||
|
reqs[1].width = viewport.width;
|
||||||
|
reqs[1].height = viewport.height;
|
||||||
|
|
||||||
if (rgb) {
|
if (rgb) {
|
||||||
mjrRenderTargetConfig config;
|
mjrRenderTargetConfig config;
|
||||||
@@ -139,22 +117,21 @@ void MjrFilamentRenderer::ReadPixels(mjrRect viewport, unsigned char* rgb,
|
|||||||
config.color_format = mjPIXEL_FORMAT_RGB8;
|
config.color_format = mjPIXEL_FORMAT_RGB8;
|
||||||
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
|
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
|
||||||
auto target = CreateRenderTarget(filament_context_.get(), config);
|
auto target = CreateRenderTarget(filament_context_.get(), config);
|
||||||
render_requests_[0].target = target.get();
|
|
||||||
render_requests_[1].target = target.get();
|
|
||||||
|
|
||||||
const size_t num_requests =
|
reqs[0].target = target.get();
|
||||||
(mode_ == FrameBufferMode::OffScreenWithGui) ? 2 : 1;
|
reqs[1].target = target.get();
|
||||||
|
|
||||||
mjrReadPixelsRequest read_request;
|
mjrReadPixelsRequest read_request;
|
||||||
mjr_defaultReadPixelsRequest(&read_request);
|
mjr_defaultReadPixelsRequest(&read_request);
|
||||||
|
read_request.target = target.get();
|
||||||
read_request.output = rgb;
|
read_request.output = rgb;
|
||||||
read_request.num_bytes = viewport.width * viewport.height * 3;
|
read_request.num_bytes = viewport.width * viewport.height * 3;
|
||||||
const mjrFrameHandle frame = filament_context_->Render(
|
|
||||||
{&render_requests_[0], num_requests}, {&read_request, 1});
|
|
||||||
filament_context_->WaitForFrame(frame);
|
|
||||||
|
|
||||||
render_requests_[0].target = nullptr;
|
const size_t num_requests =
|
||||||
render_requests_[1].target = nullptr;
|
(mode_ == FrameBufferMode::OffScreenWithGui) ? 2 : 1;
|
||||||
|
const mjrFrameHandle frame = filament_context_->Render(
|
||||||
|
{&reqs[0], num_requests}, {&read_request, 1});
|
||||||
|
filament_context_->WaitForFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (depth) {
|
if (depth) {
|
||||||
@@ -165,23 +142,19 @@ void MjrFilamentRenderer::ReadPixels(mjrRect viewport, unsigned char* rgb,
|
|||||||
config.color_format = mjPIXEL_FORMAT_R32F;
|
config.color_format = mjPIXEL_FORMAT_R32F;
|
||||||
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
|
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
|
||||||
auto target = CreateRenderTarget(filament_context_.get(), config);
|
auto target = CreateRenderTarget(filament_context_.get(), config);
|
||||||
render_requests_[0].target = target.get();
|
|
||||||
render_requests_[1].target = target.get();
|
|
||||||
|
|
||||||
mjrDrawMode last_draw_mode = render_requests_[0].draw_mode;
|
reqs[0].draw_mode = mjDRAW_MODE_DEPTH;
|
||||||
render_requests_[0].draw_mode = mjDRAW_MODE_DEPTH;
|
reqs[0].target = target.get();
|
||||||
|
|
||||||
mjrReadPixelsRequest read_request;
|
mjrReadPixelsRequest read_request;
|
||||||
mjr_defaultReadPixelsRequest(&read_request);
|
mjr_defaultReadPixelsRequest(&read_request);
|
||||||
|
read_request.target = target.get();
|
||||||
read_request.output = reinterpret_cast<uint8_t*>(depth);
|
read_request.output = reinterpret_cast<uint8_t*>(depth);
|
||||||
read_request.num_bytes = viewport.width * viewport.height * sizeof(float);
|
read_request.num_bytes = viewport.width * viewport.height * sizeof(float);
|
||||||
const mjrFrameHandle frame = filament_context_->Render(
|
|
||||||
{&render_requests_[0], 1}, {&read_request, 1});
|
|
||||||
filament_context_->WaitForFrame(frame);
|
|
||||||
|
|
||||||
render_requests_[0].target = nullptr;
|
const mjrFrameHandle frame = filament_context_->Render(
|
||||||
render_requests_[1].target = nullptr;
|
{&reqs[0], 1}, {&read_request, 1});
|
||||||
render_requests_[0].draw_mode = last_draw_mode;
|
filament_context_->WaitForFrame(frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,10 +80,9 @@ class MjrFilamentRenderer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<FilamentContext> filament_context_;
|
std::unique_ptr<FilamentContext> filament_context_;
|
||||||
FrameBufferMode mode_ = FrameBufferMode::Window;
|
|
||||||
mjrRenderRequest render_requests_[2];
|
|
||||||
std::unique_ptr<SceneBridge> scene_bridge_;
|
std::unique_ptr<SceneBridge> scene_bridge_;
|
||||||
std::unique_ptr<ImguiBridge> imgui_bridge_;
|
std::unique_ptr<ImguiBridge> imgui_bridge_;
|
||||||
|
FrameBufferMode mode_ = FrameBufferMode::Window;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mujoco
|
} // namespace mujoco
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ void SceneBridge::PrepareLights() {
|
|||||||
mjrf_setSceneSkybox(scene_.get(), model_objects_->GetSkyboxTexture());
|
mjrf_setSceneSkybox(scene_.get(), model_objects_->GetSkyboxTexture());
|
||||||
}
|
}
|
||||||
|
|
||||||
mat4 CalculateClipFromWorld(const mjrRect& viewport, const mjvGLCamera& cam) {
|
mat4 CalculateClipFromWorld(const mjrRect& viewport, const mjrCamera& cam) {
|
||||||
const float3 cam_pos(cam.pos[0], cam.pos[1], cam.pos[2]);
|
const float3 cam_pos(cam.pos[0], cam.pos[1], cam.pos[2]);
|
||||||
const float3 cam_fwd(cam.forward[0], cam.forward[1], cam.forward[2]);
|
const float3 cam_fwd(cam.forward[0], cam.forward[1], cam.forward[2]);
|
||||||
const float3 cam_up(cam.up[0], cam.up[1], cam.up[2]);
|
const float3 cam_up(cam.up[0], cam.up[1], cam.up[2]);
|
||||||
@@ -282,6 +282,13 @@ mat4 CalculateClipFromWorld(const mjrRect& viewport, const mjvGLCamera& cam) {
|
|||||||
void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) {
|
void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) {
|
||||||
mjrf_setSceneShadowsEnabled(scene_.get(), scene->flags[mjRND_SHADOW]);
|
mjrf_setSceneShadowsEnabled(scene_.get(), scene->flags[mjRND_SHADOW]);
|
||||||
mjrf_setSceneReflectionsEnabled(scene_.get(), scene->flags[mjRND_REFLECTION]);
|
mjrf_setSceneReflectionsEnabled(scene_.get(), scene->flags[mjRND_REFLECTION]);
|
||||||
|
if (scene->flags[mjRND_SEGMENT]) {
|
||||||
|
draw_mode_ = mjDRAW_MODE_SEGMENTATION;
|
||||||
|
} else if (scene->flags[mjRND_DEPTH]) {
|
||||||
|
draw_mode_ = mjDRAW_MODE_DEPTH;
|
||||||
|
} else {
|
||||||
|
draw_mode_ = mjDRAW_MODE_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
mjtNum hpos[3], hfwd[3];
|
mjtNum hpos[3], hfwd[3];
|
||||||
float headpos[3], gazedir[3];
|
float headpos[3], gazedir[3];
|
||||||
@@ -289,9 +296,8 @@ void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) {
|
|||||||
mju_n2f(headpos, hpos, 3);
|
mju_n2f(headpos, hpos, 3);
|
||||||
mju_n2f(gazedir, hfwd, 3);
|
mju_n2f(gazedir, hfwd, 3);
|
||||||
|
|
||||||
const mjvGLCamera gl_camera =
|
camera_ = mjv_averageCamera(scene->camera, scene->camera + 1);
|
||||||
mjv_averageCamera(scene->camera, scene->camera + 1);
|
clip_from_world_ = CalculateClipFromWorld(viewport, camera_);
|
||||||
clip_from_world_ = CalculateClipFromWorld(viewport, gl_camera);
|
|
||||||
|
|
||||||
// Remove all drawables from previous render and prepare new ones.
|
// Remove all drawables from previous render and prepare new ones.
|
||||||
for (auto& iter : renderables_) {
|
for (auto& iter : renderables_) {
|
||||||
@@ -365,4 +371,10 @@ void SceneBridge::SetDrawTextFunction(DrawTextAtFn fn) {
|
|||||||
draw_text_callback_ = std::move(fn);
|
draw_text_callback_ = std::move(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mjrScene* SceneBridge::GetScene() const { return scene_.get(); }
|
||||||
|
|
||||||
|
mjrCamera SceneBridge::GetCamera() const { return camera_; }
|
||||||
|
|
||||||
|
mjrDrawMode SceneBridge::GetDrawMode() const { return draw_mode_; }
|
||||||
|
|
||||||
} // namespace mujoco
|
} // namespace mujoco
|
||||||
|
|||||||
@@ -56,7 +56,9 @@ class SceneBridge {
|
|||||||
void SetDrawTextFunction(DrawTextAtFn fn);
|
void SetDrawTextFunction(DrawTextAtFn fn);
|
||||||
|
|
||||||
// Returns the managed scene.
|
// Returns the managed scene.
|
||||||
mjrScene* GetScene() const { return scene_.get(); }
|
mjrScene* GetScene() const;
|
||||||
|
mjrCamera GetCamera() const;
|
||||||
|
mjrDrawMode GetDrawMode() const;
|
||||||
|
|
||||||
SceneBridge(const SceneBridge&) = delete;
|
SceneBridge(const SceneBridge&) = delete;
|
||||||
SceneBridge& operator=(const SceneBridge&) = delete;
|
SceneBridge& operator=(const SceneBridge&) = delete;
|
||||||
@@ -71,6 +73,8 @@ class SceneBridge {
|
|||||||
|
|
||||||
mjrfContext* ctx_ = nullptr;
|
mjrfContext* ctx_ = nullptr;
|
||||||
std::unique_ptr<ModelObjects> model_objects_;
|
std::unique_ptr<ModelObjects> model_objects_;
|
||||||
|
mjrCamera camera_;
|
||||||
|
mjrDrawMode draw_mode_ = mjDRAW_MODE_COLOR;
|
||||||
DrawTextAtFn draw_text_callback_;
|
DrawTextAtFn draw_text_callback_;
|
||||||
UniquePtr<mjrScene> scene_{nullptr, nullptr};
|
UniquePtr<mjrScene> scene_{nullptr, nullptr};
|
||||||
UniquePtr<mjrLight> fallback_ibl_{nullptr, nullptr};
|
UniquePtr<mjrLight> fallback_ibl_{nullptr, nullptr};
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ static filament::ColorGrading::Builder ToBuilder(
|
|||||||
.curves(opts.shadow_gamma, opts.mid_point, opts.highlight_scale);
|
.curves(opts.shadow_gamma, opts.mid_point, opts.highlight_scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetupCamera(const mjvGLCamera& cam,
|
static void SetupCamera(const mjrCamera& cam,
|
||||||
const filament::Viewport& viewport,
|
const filament::Viewport& viewport,
|
||||||
filament::Camera* camera) {
|
filament::Camera* camera) {
|
||||||
const filament::Camera::Projection type =
|
const filament::Camera::Projection type =
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class SceneView : public mjrScene {
|
|||||||
// The target viewport for the rendered image.
|
// The target viewport for the rendered image.
|
||||||
mjrRect viewport;
|
mjrRect viewport;
|
||||||
// The camera from which to render the scene.
|
// The camera from which to render the scene.
|
||||||
mjvGLCamera camera;
|
mjrCamera camera;
|
||||||
// An optional render target into which the scene will be rendered.
|
// An optional render target into which the scene will be rendered.
|
||||||
RenderTarget* target = nullptr;
|
RenderTarget* target = nullptr;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ typedef std::uint64_t mjrFrameHandle;
|
|||||||
typedef mjtTexture mjrTextureTarget;
|
typedef mjtTexture mjrTextureTarget;
|
||||||
typedef mjtColorSpace mjrColorSpace;
|
typedef mjtColorSpace mjrColorSpace;
|
||||||
typedef mjtLightType mjrLightType;
|
typedef mjtLightType mjrLightType;
|
||||||
|
typedef mjvGLCamera mjrCamera;
|
||||||
|
|
||||||
// The textures that can be assigned to the drawable's material.
|
// The textures that can be assigned to the drawable's material.
|
||||||
struct mjrMaterialTextures {
|
struct mjrMaterialTextures {
|
||||||
@@ -367,7 +368,7 @@ struct mjrRenderRequest {
|
|||||||
mjrDrawMode draw_mode;
|
mjrDrawMode draw_mode;
|
||||||
|
|
||||||
// The camera from which to render the scene.
|
// The camera from which to render the scene.
|
||||||
mjvGLCamera camera;
|
mjrCamera camera;
|
||||||
|
|
||||||
// The dimensions of the output image.
|
// The dimensions of the output image.
|
||||||
int width;
|
int width;
|
||||||
|
|||||||
Reference in New Issue
Block a user